bigywater.tech
Java · 282

enum 的初始化顺序

枚举常量是什么时候创建的?构造器能读静态字段吗?values() 为什么每次都是新数组?构造抛异常之后会怎样?这篇用打印顺序、javap 与反射逐项验证。

常量 → $VALUES → static
枚举 的实际顺序
5 种
单独测试都触发初始化的访问方式:E.m()、E.A、values()、valueOf、E.v
ExceptionInInitializerError → NoClassDefFoundError
构造器抛异常后,第 1 次与之后访问的错误

初始化顺序

实测 · 本机执行输出
    main 开始;访问编译期常量 Color.K = 常量
    现在第一次用 Color.GREEN
    实例块(每个常量一次)
    构造器 r,this=RED,ordinal=0
    实例块(每个常量一次)
    构造器 g,this=GREEN,ordinal=1
    实例块(每个常量一次)
    构造器 b,this=BLUE,ordinal=2
    static 块:此时 VALUES 已建好,共 3 个
    g

  构造器里读静态字段(非常量):
  编译失败:
    <jtmp>/j142b/Main.java:1: error: illegal reference to static field from initializer
    public class Main { enum E { A; static int count = 0; E() { count++; } } public static void main(String[] a) { } }
                                                                ^
    1 error
  构造器里读 static final 常量: 编译通过
  构造器里用 E.n 限定名读静态字段: <jtmp>/j142d/Main.java:1: error: illegal reference to static field from initializer
public class Mai
  构造器通过静态方法读 n(绕过检查):
    0
    main:n=7

触发时机

实测 · 本机执行输出
    1 E.K(编译期常量)= 1
    2 E.class.getName() = Main$E
    3 E[] arr = new E[1]
    4 E.v
      [E 初始化]
    5 E.m()
    6 E.A
  单独只做 E.m()(静态方法): 前 → [E 初始化] → 后
  单独只做 E.A(枚举常量): 前 → [E 初始化] → 后
  单独只做 E.values(): 前 → [E 初始化] → 后
  单独只做 E.valueOf("A"): 前 → [E 初始化] → 后
  单独只做 E.v(非常量静态字段): 前 → [E 初始化] → 后
  Class.forName 的 initialize 参数:
    Class.forName(E, false)
    Class.forName(E, true)
      [E 初始化]
  EnumSet.noneOf(E.class) 会触发初始化吗:
    switch 前
    没碰 E
      [E 初始化]
    0

字节码

实测 · 本机执行输出
  生成的 class 文件: ['Main$Color.class', 'Main$Op$1.class', 'Main$Op$2.class', 'Main$Op.class', 'Main.class']
  javap -p Main$Color:
     final class Main$Color extends java.lang.Enum<Main$Color> {
     public static final Main$Color RED;
     public static final Main$Color GREEN;
     private static final Main$Color[] $VALUES;
     public static Main$Color[] values();
     public static Main$Color valueOf(java.lang.String);
     private Main$Color();
     private static Main$Color[] $values();
     static {};
     }
  javap -p Main$Op:
     abstract class Main$Op extends java.lang.Enum<Main$Op> {
     public static final Main$Op ADD;
     public static final Main$Op MUL;
     private static final Main$Op[] $VALUES;
     public static Main$Op[] values();
     public static Main$Op valueOf(java.lang.String);
     private Main$Op();
     abstract int ap(int, int);
     private static Main$Op[] $values();
     static {};
     }
  javap -p Main$Op$1:
     final class Main$Op$1 extends Main$Op {
     private Main$Op$1(java.lang.String, int);
     int ap(int, int);
     }
  Main$Color 的 <clinit> 字节码前几行:
     0: new #1 // class Main$Color
     3: dup
     4: ldc #29 // String RED
     6: iconst_0
     7: invokespecial #30 // Method "<init>":(Ljava/lang/String;I)V
     10: putstatic #3 // Field RED:LMain$Color;
     13: new #1 // class Main$Color
     16: dup
     17: ldc #31 // String GREEN
     19: iconst_1
     20: invokespecial #30 // Method "<init>":(Ljava/lang/String;I)V
     23: putstatic #7 // Field GREEN:LMain$Color;
     26: invokestatic #32 // Method $values:()[LMain$Color;
     29: putstatic #10 // Field $VALUES:[LMain$Color;
     32: return
     }
  values() 的字节码:
     Code:
     0: getstatic #10 // Field $VALUES:[LMain$Color;
     3: invokevirtual #14 // Method "[LMain$Color;".clone:()Ljava/lang/Object;
     6: checkcast #15 // class "[LMain$Color;"
     9: areturn
  switch 用到的合成类 Main$1:

反射

实测 · 本机执行输出
    values() 两次调用是同一个数组吗:false,内容相同:true
    改掉返回数组后再取:[RED, GREEN]
    valueOf(PINK):java.lang.IllegalArgumentException: No enum constant Main.Color.PINK
    valueOf(null):java.lang.NullPointerException: Name is null
    valueOf(red):IllegalArgumentException
    构造器参数:[class java.lang.String, int]
    反射 newInstance:java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    getMethod(clone):NoSuchMethodException
    getEnumConstants == values 内容:true;getEnumConstants 两次是同一个数组:false
    Enum.valueOf(String.class):IllegalArgumentException java.lang.String is not an enum class

初始化失败与循环

实测 · 本机执行输出
    第 1 次:ExceptionInInitializerError:null  cause=java.lang.IllegalStateException: 构造 B 时失败
    第 2 次:NoClassDefFoundError:Could not initialize class Main$Bad  cause=java.lang.ExceptionInInitializerError: Exception java.lang.IllegalStateException: 构造 B 时失败 [in threa
    第 3 次:NoClassDefFoundError:Could not initialize class Main$Bad  cause=java.lang.ExceptionInInitializerError: Exception java.lang.IllegalStateException: 构造 B 时失败 [in threa
  X 的构造里引用 Y,Y 的构造里引用 X(循环):
    先触发 X:X.A.y = P;Y.P.x = null
  对照:普通类里 new 自身的静态字段先于 W 赋值:
    类里的 static 字段顺序:seen(W)=0 seenZ(常量)=5 最终 W=5

分步演示

枚举初始化
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,javac 默认选项
    • 没有验证:JDK 其它版本(尤其更早版本)生成的 $VALUES / $values() 形态、本例没有生成 Main$1 的原因(没有用 javac 选项对比)、多线程并发首次访问枚举的初始化锁行为、序列化与枚举单例、GraalVM / AOT 对枚举初始化的处理
    京ICP备2026067502号-1京公网安备11010502063487号