使用枚举的正确姿势

简介: 使用枚举的正确姿势

枚举是JDK1.5引入的新特性。被enum关键字修饰的类就是一个枚举类。

关于枚举,阿里巴巴开发手册有这样两条建议:

  1. 枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
  2. 如果变量值仅在一个固定范围内变化用 enum 类型来定义。

一 枚举类有哪些特点

创建一个ColorEnum的枚举类,通过编译,再反编译看看它发生了哪些变化。

public enum ColorEnum {
    RED,GREEN,BULE;
}

使用命令javac ColorEnum.java进行编译生成class文件,然后再用命令javap -p ColorEnum.class进行反编译。

去掉包名,反编译后的内容如下:

public final class ColorEnum extends Enum{
    public static final ColorEnum GREEN;
    public static final ColorEnum BULE;
    private static final ColorEnum[] $VALUES;
    public static ColorEnum[] values();
    public static ColorEnum valueOf(java.lang.String);
    private ColorEnum();
    static {};
}
  1. 枚举类被final修饰,因此枚举类不能被继承;
  2. 枚举类默认继承了Enum类,java不支持多继承,因此枚举类不能继承其他类;
  3. 枚举类的构造器是private修饰的,因此其他类不能通过构造器来获取对象;
  4. 枚举类的成员变量是static修饰的,可以用类名.变量来获取对象;
  5. values()方法是获取所有的枚举实例;
  6. valueOf(java.lang.String)是根据名称获取对应的实例;

二 枚举创建线程安全的单例模式

public enum  SingletonEnum {
    
    INSTANCE;
    
    public void doSomething(){
        // dosomething...
    }
}

这样一个单例模式就创建好了,通过SingletonEnum.INSTANCE来获取对象就可以了。

2.1 序列化造成单例模式不安全

一个类如果如果实现了序列化接口,则可能破坏单例。每次反序列化一个序列化的一个实例对象都会创建一个新的实例。

枚举序列化是由JVM保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.EnumvalueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObjectreadObjectreadObjectNoDatawriteReplacereadResolve等方法,从而保证了枚举实例的唯一性。

2.2 反射造成单例模式不安全

通过反射强行调用私有构造器来生成实例对象,造成单例模式不安全。

Class<?> aClass = Class.forName("xx.xx.xx");
Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
SingletonEnum singleton = (SingletonEnum) constructor.newInstance("Java旅途");

但是使用枚举创建的单例完全不用考虑这个问题,来看看newInstance的源码!

public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    // 如果是枚举类型,直接抛出异常,不让创建实例对象!
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

如果是enum类型,则直接抛出异常Cannot reflectively create enum objects,无法通过反射创建实例对象!

三 通过枚举消除if/else

假如要写一套加密接口,分别给小程序、app和web端来使用,但是这三种客户端的加密方式不一样。一般情况下我们会传一个类型type来判断来源,然后调用对应的解密方法即可。代码如下:

if("WEIXIN".equals(type)){
    // dosomething
}else if("APP".equals(type)){
    // dosomething
}else if("WEB".equals(type)){
    // dosomething
}

现在使用枚举来消除这些if/else。

写一个加密用的接口,有加密和解密两个方法。然后用不同的算法去实现这个接口完成加解密。

public interface Util {
 
    // 解密
    String decrypt();
    
    // 加密
    String encrypt();
}

创建一个枚举类来实现这个接口

public enum UtilEnum implements Util {

    WEIXIN {
        @Override
        public String decrypt() {
            return "微信解密";
        }

        @Override
        public String encrypt() {
            return "微信加密";
        }
    },
    APP {
        @Override
        public String decrypt() {
            return "app解密";
        }

        @Override
        public String encrypt() {
            return "app加密";
        }
    },
    WEB {
        @Override
        public String decrypt() {
            return "web解密";
        }

        @Override
        public String encrypt() {
            return "web加密";
        }
    };
}

最后,获取到type后,直接调用解密方法就行了。

String decryptMessage = UtilEnum.valueOf(type).decrypt();

以后,如果新增了一个其他加密方式,只需要修改上面的枚举类就完成了,业务代码都不需要改动。

这就是枚举类比较高级的两个用法。

点关注、不迷路

如果觉得文章不错,欢迎关注点赞收藏,你们的支持是我创作的动力,感谢大家。

如果文章写的有问题,请不要吝啬,欢迎留言指出,我会及时核查修改。

如果你还想更加深入的了解我,可以微信搜索「Java旅途」进行关注。回复「1024」即可获得学习视频及精美电子书。每天7:30准时推送技术文章,让你的上班路不在孤独,而且每月还有送书活动,助你提升硬实力!

目录
相关文章
|
存储 Kubernetes 算法
开源免费的对象存储Minio
Minio是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合存储大容量、非结构化的数据。例如,图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小的,从几KB到5TB不等。
1946 0
|
3月前
|
安全 开发工具 git
如何回滚Git中的提交?
如何回滚Git中的提交?
789 0
|
编解码 前端开发 图形学
采用Canvas Scaler与锚点系统实现UI自适应多屏幕分辨率
【7月更文第10天】在游戏开发或应用设计中,确保用户界面(UI)能够在不同屏幕分辨率和纵横比上保持良好显示效果是一项基本要求。Unity 引擎通过其强大的 UI 系统,特别是 Canvas Scaler 和锚点系统,为开发者提供了实现这一目标的高效工具。本文将深入探讨如何结合使用这两个功能来创建自适应UI布局,以适配广泛的设备屏幕。
715 0
|
11月前
|
Java Unix Linux
Android Studio中Terminal运行./gradlew clean build提示错误信息
遇到 `./gradlew clean build`命令执行出错时,首先应检查错误信息的具体内容,这通常会指向问题的根源。从权限、环境配置、依赖下载、版本兼容性到项目配置本身,逐一排查并应用相应的解决措施。记住,保持耐心,逐步解决问题,往往复杂问题都是由简单原因引起的。
958 2
|
JavaScript 中间件 数据库
中间件应用身份验证和授权
【5月更文挑战第1天】你可以编写类似的中间件函数来检查用户的角色和权限,并根据需要允许或拒绝访问。
231 2
中间件应用身份验证和授权
|
安全 API 数据处理
Android 15革命来袭:64位时代的大门轰然开启,开发者和用户将何去何从?
【8月更文挑战第20天】随着性能提升,Android 15的重大更新引领64位时代,提供更大内存支持、更快执行速度及增强安全性。新版本淘汰32位应用,优化系统库,并改善内存管理。开发者需适应64位开发,面对应用体积增大等挑战,同时享受更高效能。此转变标志着移动应用开发步入新阶段,为用户带来更流畅安全的体验。
718 0
|
Java 程序员 数据库连接
Java中的异常处理:理解try-catch块的工作原理
本文深入探讨了Java编程语言中异常处理的核心机制——try-catch块。我们将通过具体示例,详细解释异常的产生、捕获和处理过程,以及如何有效地利用这一机制来提高代码的健壮性和可维护性。
|
存储 Linux 网络安全
杨校老师课堂之云计算私有云OpenStack框架快速搭建
杨校老师课堂之云计算私有云OpenStack框架快速搭建
434 0
|
Java Spring 容器
同一接口有多个实现类,怎么来注入一个指定的实现?@Resource、@Autowired、@Qualifier
同一接口有多个实现类,怎么来注入一个指定的实现?@Resource、@Autowired、@Qualifier
899 0
|
机器学习/深度学习 存储 大数据
大数据时代的处理挑战与突破
随着数字化时代的到来,大数据已经成为了企业和组织获取商业价值的关键资源。然而,大规模数据处理也带来了很多挑战,如数据量巨大、数据质量不一、处理效率低下等。本文将探讨大规模数据处理所面临的挑战,并介绍几种处理大规模数据的方法和技术,包括分布式系统、基于内存的计算、图形数据库、NoSQL数据库和机器学习算法等。同时,本文还将重点介绍近年来在大规模数据处理领域中的突破,如深度学习、人工智能等技术的应用。
445 1