反编译分析Java枚举类型的实现
1 | // Enum JDK 源码 |
抽象类不能被实例化,所以我们在java程序中不能使用new关键字来声明一个Enum,如果想要定义可以使用这样的语法:
1 | enum enumName{ |
注意,此处的enum是抽象类,但是无法通过编程来继承.
1 | public class TestEnumExtend extends Enum{ |
编译文件会报错:
1 | $ javac TestEnumExtend.java |
怎么理解<E extends Enum>
Enum<E extends Enum
1 | enum Color{ |
因为枚举类型的默认的序号都是从零开始的,显然 Color.RED.ordinal()和Season.SPRING.ordinal()的值都是0.
注意一下 compareTo
方法,首先我们认为Enum的定义中没有使用Enum<E extends Enum<E>>
,那么compareTo方法就要这样定义(因为没有使用泛型,所以就要使用Object,这也是Java中很多方法常用的方式):
1 | // 不使用泛型 |
如果在compareTo方法中不做任何处理的话,那么 Color.RED和Season.SPRING相等的(因为Season.SPRING的序号和Color.RED的序号都是 0 )。但是,很明显, Color.RED和Season.SPRING并不相等。
但是Java使用Enum<E extends EnumColor.RED.compareTo(Season.SPRING);
这样的代码是,会报出这样的错误:
1 | The method compareTo(Color) in the type Enum<Color> is not applicable for the arguments (Season) |
反编译
1 | public enum Fruit { |
可以看到,实际上在经过编译器编译后生成了一个 Fruit 类,该类继承自 Enum 类,且是 final 的。从这一点来看,Java 中的枚举类型似乎就是一个语法糖。
每一个枚举常量都对应类中的一个 public static final 的实例,这些实例的初始化应该是在 static {} 语句块中进行的。因为枚举常量都是 final 的,因而一旦创建之后就不能进行更改了。
此外,Fruit 类还实现了 values() 和 valueOf() 这两个静态方法。
1 | // Decompiled by Jad, $ jad -sjava Fruit.class |
除了对应的四个枚举常量外,还有一个私有的数组,数组中的元素就是枚举常量。编译器自动生成了一个 private 的构造方法,这个构造方法中直接调用父类的构造方法,传入了一个字符串和一个整型变量。从初始化语句中可以看到,字符串的值就是声明枚举常量时使用的名称,而整型变量分别是它们的顺序(从0开始)。枚举类的实现使用了一种多例模式,只有有限的对象可以创建,无法显示调用构造方法创建对象。
values() 方法返回枚举常量数组的一个浅拷贝,可以通过这个数组访问所有的枚举常量;而 valueOf() 则直接调用父类的静态方法 Enum.valueOf(),根据传入的名称字符串获得对应的枚举对象。
枚举类的多态
枚举类无法实现继承,但是可以实现接口。
1 | interface WhatName { |
上述代码会扩充编译器生成的源码,以匿名内部类的方式对Fruit进行了Overide:
1 | APPLE = new Fruit("APPLE", 0); |