通过反编译字节码来理解 Java 枚举

枚举的声明很简单, 像 enum Gender { Male, Female }, 其余事情就是 Java 编译器帮我们干的了,所以 enum 也就是一块语法糖。有了枚举确实是很方便,避免了传统常量的无范围性。那么编译器到底在后面做了什么呢?以及理解了这个之后我们可以怎么去使用 Java 的枚举, 下面就从这个例子说起:

public enum Gender {
    Male,
    Female
}

把上面的编译成 Gender.class, 然后用  javap -c Gender 反编译出来就是

从上面的字节码能够粗略的知道以下几点

  1. Gender 是 final 的
  2. Gender 继承自 java.lang.Enum 类
  3. 声明了字段对应的两个 static final Gender 的实例
  4. 实现了 values() 和  valueOf(String) 静态方法
  5. static{} 对所有成员进行初始化

有了以上的字节码,我们作进一步还原出 Gender 的普通类大概是这样的:

private Gender(String name, int origianl) 是我加上去的,是为了模拟枚举实例的创建,其实实例都是在 static 块中创建的。

当然上面的那个类是无法被编译的,因为 Java  编译器限制了我们显式的继承自 java.Lang.Enum 类, 报错 "The type Gender may not subclass Enum explicitly", 虽然 java.Lang.Enum 声明的是

public abstract class Enum>
        implements Comparable, Serializable

这样看来枚举类其实用了多例模式,枚举类的实例是有范围限制的。它同样像我们的传统常量类,只是它的元素是有限的枚举类本身的实例。它继承自 java.lang.Enum, 所以可以直接调用 java.lang.Enum 的方法,如 name(), original() 等,name 就是常量名称, original 与 C 的枚举一样的编号。

可以在枚举类中自定义构造方法,但必须是  private 或  package protected, 因为枚举本质上是不允许在外面用 new Gender() 方式来构造实例的(Cannot instantiate the type Gender),否则也是要乱了套的。

我们可以在枚举中定义自己的类,或实例方法,比如对于   enum Gender, 方法会直接定义给 Gender 类或实例上去。

由于 enum 隐式继承自 java.lang.Enum 类,且 Java 不接受类的多重继承,所以我们不能让 enum 再继承别的类,下面那样写是错误的

enum Gender extends Security {} //Syntax error on token "extends", implements expected

不能类多重继承,那枚举可以实现接口啊,因此下面的写法是正确的

public enum Gender implements Behavior {}

让枚举实现接口有时候可以让设计变得巧妙。

结合枚举实现接口以及自定义方法,可以写出下面那样的代码:

方法可以定义成所有实例公有,也可以让个别元素独有。

需要特别注明一下,上面在 Male {} 声明一个 print() 方法后实际产生一个 Gender 的匿名子类,编译后的 Gender$1,反编译它 javap -c Gender\$1:

所以在 emum Gender 那个枚举中的成员  Male 相当于是

上面 4: Invokespecial #1 要调用到下面的 Gender(java.lang.String, int, Gender$1) 方法。

若要研究完整的 Male 元素的初始化过程就得 javap -c Gender 看 Gender.java 产生的所有字节码,在此列出片断

在 static{} 中大致看下 Male 的初始过程:加载 Gender$1, 并调用它的 Gender$1(java.lang.String, int) 构造函数生成一个 Gender$1 实例赋给 Male 属性。

JDK 提供有两个关于枚举的工具类  EnumSetEnumMap, 实际中可以好好发掘发掘一下。

附:1. Java enum的用法详解
      2. Java 语言中 Enum 类型的使用介绍
      3. Guide for understanding enum in java

 

本文链接 https://yanbin.blog/understand-java-enum-with-bytecode/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

1 Comment
Inline Feedbacks
View all comments
trackback

[…] 附录拓展: 深入理解 Java 对象序列化 对象的序列化和反序列化 通过反编译字节码来理解 Java 枚举 […]