你所不知的 java 枚举

作者: EricAlpha | 来源:发表于2017-04-20 15:55 被阅读513次

首先给出文章的结论:

  • 枚举是类类型
  • 每个枚举常量都是所属枚举类的对象
  • 枚举类在加载完成后,无法再进行实例化操作
  • 枚举是线程安全的

java 中的枚举在实现上非常简单,以下就是一个枚举的例子:

public enum FruitEnum {
    APPLE
}

但是如果反编译 FruitEnum.class,会发现编译器在背后默默的做了大量的工作,以下是反编译结果:

public final class FruitEnum extends Enum {
    public  static final FruitEnum APPLE;
    private static final FruitEnum $VALUES[];

    private FruitEnum(String s, int i) {
        super(s, i);
    }
  
    public static FruitEnum[] values() {
        return (FruitEnum[])$VALUES.clone();
    }

    public static FruitEnum valueOf(String name) {
        return (FruitEnum)Enum.valueOf(FruitEnum, name);
    }
  
    static {
        APPLE = new FruitEnum("APPLE", 0);
        $VALUES = (new FruitEnum[] {
            APPLE
        });
    }
}

下面根据反编译结果说明枚举的几个特性。

  • 枚举的实际类型
public final class FruitEnum extends Enum

说明枚举是类,并且是用 final 修饰的类,意味着枚举不能再被继承扩展。而我们声明枚举类时使用的 enum 只是一个关键字。

其次,所有的枚举都继承了一个基类 Enum ,该基类为枚举提供了一些通用方法。

  • 枚举的构造函数

在 FruitEnum 中,我们并没有定义构造函数,但在反编译的代码中,我们发现编译器自动帮我们添加了以下私有构造函数:

private FruitEnum(String s, int i) {
    super(s, i);
}

该私有构造函数只是简单调用了父类 Enum 的构造函数:

protected Enum(String name, int ordinal) {
    this.name = name;       // 枚举常量的名称
    this.ordinal = ordinal; // 枚举常量在枚举类的位置,从0开始
}

实际上,编译器只允许私有的枚举类构造函数,并且显示定义的枚举构造函数经编译器后,都会被添加 name 和 ordinal 2个参数,用以调用父类 Enum 的构造函数。

  • 枚举常量

再看反编译后的 FruitEnum 中的成员变量:

public  static final FruitEnum APPLE;
private static final FruitEnum $VALUES[];

其中 FruitEnum APPLE 对应我们定义的枚举常量 APPLE,而 FruitEnum $VALUES[] 则是容纳所有枚举常量的数组。

这两个变量初始化操作如下:

static {
    APPLE = new FruitEnum("APPLE", 0);
    $VALUES = (new FruitEnum[] { APPLE });
}

可知,每个枚举常量都是定义它的类的对象,各枚举对象都是用 public static final 修饰的,这也是枚举对象被称为枚举常量的原因。

其次,FruitEnum APPLEFruitEnum $VALUES[] 都是在静态代码块中进行初始化,因此在 JVM 记载完成枚举类后,各枚举常量都已被

创建完毕。由于枚举类中只允许出现私有构造函数,固无法再实例化新的枚举对象。

  • 枚举类实例化

无法通过构造函数这种一般手段实例化枚举类,那么通过反射呢?

先尝试以下形式:

Class<FruitEnum> fruitEnumClazz = FruitEnum.class;
FruitEnum fruitDemo1 = fruitEnumClazz.newInstance();

抛出了以下错误:

因为 Class.newInstance 内部是通过目标Class的 无参构造函数 创建实例的,而上文已经说了 FruitEnum 的实际构造函数是:

private FruitEnum(String s, int i) {
    super(s, i);
}

固会找不到对应的构造函数,抛出异常。

换种反射方式,先获取正确的构造函数:

// 获取 FruitEnum 的构造函数
Constructor<FruitEnum> fruitEnumConstructor = fruitEnumClazz.getDeclaredConstructor(String.class, int.class);
// 获取访问权限
fruitEnumConstructor.setAccessible(true);
// 实例化
FruitEnum fruitDemo2 = fruitEnumConstructor.newInstance();

成功获取了 FruitEnum 的构造函数,然而还是抛出了异常:

在 JVM 层面禁止了通过反射构造枚举实例的行为。

然而除了反射外,还可以通过 Object.clone() 方法克隆一个已存在的对象。但是这种方式也是不行的,看 Enum 类中的 clone() 方法:

protected final Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

Enum 类重写了 Obejct 的 clone()方法,只要尝试通过 clone() 方法构造枚举对象,都会抛出异常,并且该方法被设置为不能再重写。

  • 单例

从编译器、JVM,再到 java 内部设计,层层把关,封死了实例化枚举类的这一企图。因此有一种作法是通过枚举的形式实现单例,下面给个示例:

public class EnumSingleton{
    private EnumSingleton() {
      
    }
  
    public static EnumSingleton getInstance(){
        return Singleton.INSTANCE.getInstance();
    }
    
    private static enum Singleton {
        INSTANCE;
        
        private EnumSingleton singleton;
        
        // JVM 保证了此方法绝对只调用一次
        private Singleton(){
            singleton = new EnumSingleton();
        }
        
        public EnumSingleton getInstance(){
            return singleton;
        }
    }
}
  • 线程安全

    枚举常量都是 static 类型的,在枚举类加载完成后,会进行枚举常量的初始化,之后枚举类无法再实例化和修改。java 的类加载、初始化过程是线程安全的,因此创建一个 enum 是线程安全的。

相关文章

  • 你所不知的 java 枚举

    首先给出文章的结论: 枚举是类类型 每个枚举常量都是所属枚举类的对象 枚举类在加载完成后,无法再进行实例化操作 枚...

  • 枚举

    一、枚举 我们所定义的每个枚举类型都继承自java.lang.Enum类。枚举中的每个成员默认都是publiic ...

  • 单例模式(Java内部类加载顺序)

    你真的会写单例模式吗——Java实现Android设计模式源码解析之单例模式深度分析 Java 的枚举类型:枚举的...

  • 枚举

    枚举 wiki Java的枚举类型用法介绍 深入理解Java枚举类型(enum) 为什么要用enum? 学习计划 ...

  • Java枚举

    Java中的每个枚举都是java.lang.Enum的final子类,枚举类中的每个枚举常量都是该枚举类的一个实例...

  • Java核心类-枚举类

    Java 进阶——枚举enum使用小结及使用枚举替代你的常量类 通过enum定义的枚举类,和其他的class没有任...

  • 枚举的花式使用方法

    本文参照《On Java8》 基本的枚举特性 枚举除了简单的应用之外,我们还可以使用它的基本的特性。 如上述程序所...

  • Kotlin基础---枚举类

    Java的枚举 Kotlin的枚举 枚举是极少数Kotlin声明比Java使用了更多的关键字的例子Kotlin用了...

  • 枚举学习

    java enum枚举类 enum(枚举)类介绍 **java枚举类是一组预定义常量的集合,使用enum关键字声明...

  • Java枚举类

    枚举其实是个特殊的 Java 类,创建枚举需用enum关键字,枚举类都是java.lang.Enum的子类,jav...

网友评论

  • 华枯荣:excited。但是工程中,很少看见用枚举实现单例。
  • random_good:确实不知道,还能用枚举实现单例

本文标题:你所不知的 java 枚举

本文链接:https://www.haomeiwen.com/subject/tbelzttx.html