java 枚举原理-用javap分析class
1 示例代码
源码分析的示例代码如下
2 枚举原理
枚举类在编译后生成了一个抽象类 继承了ENUM,他的变量生成一个内部类继承了这个抽象类 然后实现了枚举的接口(如果该枚举类有interface),
内部类是抽象类的一个静态 final 的变量
内部类继承了抽象类提供的枚举类里的方法和ENUM的方法
2.1 Class文件
在编译后生成了的class文件如下,多了两个匿名内部类class文件。
2.1 枚举类的javap*****
上图可以看出这是继承了java.lang.Enum 且impl 定义的接口
但是这个是abstract 抽象类
所以一个枚举类不能继承其他类 但是可以实现多个接口
上图是其方法
上图是其构造方法 可以看出调用父类构造方法
多的两个 string int 是父类(Enum)的name ordinal
2.1 匿名内部类的javap*****
下图可以看出 编译后生成的子类 继承了抽象类 并且实现了接口的方法
2.1 java.lang.Enum<>
构造方法的name ordinal ,name就是枚举的变量名 如下图HANDLERONE
用名 来判断一个枚举里面是否包含
Enum禁用克隆
2.1 加载流程*****
看指令
0 New 匿名内部类
4 弹变量HANDLERONE到栈
7/9 弹变量(构造方法里的变量)到栈
11 调用自己的构造方法
14 赋值变量HANDLERON
17-31 重复HANDLERONE 来初始化HANDLERTWO
35 抽象类handlerrstrategies生成一个数组(VALUES变量)
40 获取HANDLERONE
43 存储
44 压栈
45-50 重复HANDLERONE 来把HANDLERTWO放到数组
Tips: 后面的数组因该就是抽象类handlerrstrategies的 VALUES变量
2.1 Main()测试
3 单例模式-枚举类
3.1 枚举类实现单例模式
3.2 原理分析
因为Java虚拟机会保证枚举对象的唯一性,因此每一个枚举类型和定义的枚举变量在JVM中都是唯一的。这是因为通过枚举原理可以看出,枚举类的变量的值就是静态内部类的初始化
Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。
为何要使用枚举类实现单例模式,是因为枚举类可以避免序列化和反射实例化多个的情况。
在序列化中,通过ObjectInputStream的readObject0()方法中可以看到调用了readEnum()方法,而readEnum是通过类名和类对象类找到一个唯一的枚举对象,找到后赋值返回。
而反射的newInstance是直接不实例化枚举类的对象,会直接报“Cannot reflectively create enum objects”,即不能用反射来创建枚举类型。
因此Effective JAVA书中推荐使用枚举类实现单例模式。
4 小结
1.枚举本质上是通过普通的类来实现的,只是编译器为我们进行了处理。
2.每个枚举类型都继承自java.lang.Enum,并自动添加了values和valueOf方法。
3.每个枚举常量是一个静态常量字段,使用内部类实现,该内部类继承了枚举类。
4.所有枚举常量都通过静态代码块来进行初始化,即在类加载期间就初始化。
5.通过把clone、readObject、writeObject这三个方法定义为final的,同时实现是抛出相应的异常。这样保证了每个枚举类型及枚举常量都是不可变的。可以利用枚举的这两个特性来实现线程安全的单例。