文章目录
一、枚举的本质
-
在 java 中,经常使用 enum 来表示一组具名的值的有限集合。
-
枚举的本质
- 举例说明:
像下面这个枚举类:
package info.yingying.practice.test.enums; public enum TestEnum { HELLO_WORLD("helloWorld", "ok"), HI("hi", "ko"); TestEnum(String s1, String s2) { } }
在使用 javac TestEnum.java 编译之后,会得到一个字节码文件 TestEnum.class ,再利用 javap TestEnum.class 反编译字节码文件,效果如下:
C:\Users\mayingying12\Desktop\马莹莹资料\IdeaProjects\practice\src\info\yingying\practice\test\enums>javap TestEnum.class Compiled from "TestEnum.java" public final class info.yingying.practice.test.enums.TestEnum extends java.lang.Enum<info.yingying.practice.test.enums.TestEnum> { public static final info.yingying.practice.test.enums.TestEnum HELLO_WORLD; public static final info.yingying.practice.test.enums.TestEnum HI; public static info.yingying.practice.test.enums.TestEnum[] values(); public static info.yingying.practice.test.enums.TestEnum valueOf(java.lang.String); static {}; }
由于 javap 命令只可以看到 public 部分,并且并没有看到此类的构造方法,那会不会是因为构造函数被 javac 声明成 private 的了呢?
我们再使用 javap -p TestEnum.class 查看私有方法和域,效果如下:
C:\Users\mayingying12\Desktop\马莹莹资料\IdeaProjects\practice\src\info\yingying\practice\test\enums>javap -p TestEnum.class Compiled from "TestEnum.java" public final class info.yingying.practice.test.enums.TestEnum extends java.lang.Enum<info.yingying.practice.test.enums.TestEnum> { public static final info.yingying.practice.test.enums.TestEnum HELLO_WORLD; public static final info.yingying.practice.test.enums.TestEnum HI; private static final info.yingying.practice.test.enums.TestEnum[] $VALUES; public static info.yingying.practice.test.enums.TestEnum[] values(); public static info.yingying.practice.test.enums.TestEnum valueOf(java.lang.String); private info.yingying.practice.test.enums.TestEnum(java.lang.String, java.lang.String); static {}; }
因此,我们可以总结出:
① 枚举实际上是 final class 的 java 类,这也符合枚举不可被继承的特点。 ② 同时 Enum 继承了 java.lang.Enum 类,由于 java 是单继承的,这意味着枚举不可以继承其他类了。 ③ 枚举中的值都被 final 修饰,以维护不可变性,并且是 public static 的,可以直接访问他们。 ④ 枚举类的构造函数被声明为 private,这意味着用户不可以实例化枚举类。
问题:
① static{} 是用来做什么的 ② 值是什么时候被初始化的 ③ values() 方法是用来做什么的
举例:
public enum Color { RED, GREEN, BLUE; } public final class Color extends Enum<Color> { public static final Color RED; public static final Color GREEN; public static final Color BLUE; // ---------- javac ---------- private static final Color[] $VALUES; public static Color[] values() { return $VALUES.clone(); } // --------------------------- // ---------- ECJ ---------- // private static final Color[] ENUM$VALUES; // // public static Color[] values() { // Color[] copy = new Color[ENUM$VALUES.length]; // System.arraycopy(ENUM$VALUES, 0, copy, 0, ENUM$VALUES.length); // return copy; // } // ------------------------- public static Color valueOf(String name) { return (Color) Enum.valueOf(Color.class, name); } private Color(String name, int ordinal) { super(name, ordinal); } static { RED = new Color("RED", 0); GREEN = new Color("GREEN", 1); BLUE = new Color("BLUE", 2); $VALUES = new Color[3]; $VALUES[0] = RED; $VALUES[1] = GREEN; $VALUES[2] = BLUE; } }
结论:
① 若未自动添加构造函数,编译时会自动生成 private 的构造函数,接收两个参数,一个是枚举对象的名字,一个是位置,在构造函数中直接调用了super(String, int),即java.lang.Enum的protected构造函数来构造对象。 ② 编译时会自动添加 static 代码块儿,来初始化所有的枚举对象,并添加到自动生成的一个数组常量中储存起来, ECJ 编译的是 ENUM$VALUES,javac 编译的是 $VALUES。 ③ 编译时会自动生成 public static 的 values() 方法-返回所有枚举对象的数组,也是在编译时期隐式添加的。 ④ 编译时会自动生成 public static 的 valueOf(String) 方法,直接调用了 Enum.valueOf() 方法。
注意:
- 枚举对象的构造和赋值先于所有的 static 代码
- 枚举类的初始化和枚举对象的初始化过程和普通的类完全不同。普通的类是先完成类的初始化,然后才构造对象,构造对象的时候所有的静态成员都已经准备好了;但是枚举不同,枚举类初始化的第一步就是要构造所有的枚举对象,也就是说,枚举对象构造时,静态成员还完全没有准备好,这也是为什么在枚举对象的构造函数中,引用非final的static成员会编译不通过。