Enum类源码剖析 一、Enum类的概述 Java中的枚举类型默认都继承自Enum抽象类,因此了解Enum类的原理对于枚举类型的学习大有帮助。Enum
的全写是Enumeration
,这个词的翻译是列举、逐条陈述、细目。枚举类型是JDK 5
之后引进的一种非常重要的引用类型,可以用来定义一系列枚举常量。在没有引入enum
关键字之前,要表示可枚举的变量,只能使用以下的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class SeasonDef { public static final int SPRING = 1 ; public static final int SUMMER = 2 ; public static final int AUTUMN = 3 ; public static final int WINTER = 4 ; public static String getSeason (int type) { String season = "" ; switch (type) { case SPRING: season = "春天" ; break ; case SUMMER: season = "夏天" ; break ; case AUTUMN: season = "秋天" ; break ; case WINTER: season = "冬天" ; break ; default : throw new RuntimeException ("type out of range!" ); } return season; } public static void main (String[] args) { System.out.println(SeasonDef.getSeason(SeasonDef.SPRING)); System.out.println(SeasonDef.getSeason(1 )); System.out.println(SeasonDef.getSeason(5 )); } }
这种实现方式有几个弊端
:
类型不安全
。试想一下,有一个方法期待接受一个季节作为参数,那么只能将参数类型声明为int
,但是传入的值可能是5
。显然只能在运行时进行参数合理性的判断,无法在编译期间完成检查。
指意性不强,含义不明确
。我们使用枚举,很多场合会用到该枚举的字串符表达,而上述的实现中只能得到一个数字,不能直观地表达该枚举常量的含义。当然也可用String
常量,但是又会带来性能问题,因为比较要依赖字符串的比较操作
。
使用enum
来表示枚举可以更好地保证程序的类型安全和可读性。
enum
是类型安全的。除了预先定义的枚举常量,不能将其它的值赋给枚举变量。这和用int
或String
实现的枚举很不一样。
enum
有自己的名称空间,且可读性强。在创建enum
时,编译器会自动添加一些有用的特性。每个enum
实例都有一个名字(name
)和一个序号(ordinal
),可以通过toString()
方法获取enum
实例的字符串表示。还以通过values()
方法获得一个由enum
常量按顺序构成的数组。
enum
还有一个特别实用的特性,可以在switch
语句中使用,这也是enum
最常用的使用方式了。
二、Enum类的继承关系 Enum抽象类实现了Comparable
和Serializable
接口,其中覆写的compareTo
方法比较的是两个枚举类的序号
即ordinal
(后面会提到)。这里有意思的是泛型参数E extends Enum<E>
,表明Enum类存放的是Enum<E>自身及其子类
,例如声明一个枚举类型enum Color
,编译器展开其实就是class Color extends Enum<Color>
,这里的泛型参数起到了规定诸如compareTo
方法中参数类型的作用,使其只能为Enum<E>自身及其子类
。
1 2 public abstract class Enum <E extends Enum <E>> implements Comparable <E>, Serializable
三、Enum类的字段及构造方法 Enum类的字段很简明,就是枚举常量名称name
和枚举常量序号oridinal
,后面分析枚举类型的反编译会看到具体细节。
1 2 3 4 5 6 7 8 9 10 11 private final String name;private final int ordinal;public final String name () { return name; } public final int ordinal () { return ordinal; }
注意到Enum类的构造方法是protected
权限,因此子类可以直接使用(后面分析枚举类型的反编译会看到具体细节)。
1 2 3 4 protected Enum (String name, int ordinal) { this .name = name; this .ordinal = ordinal; }
四、Enum类的重要方法 compareTo
方法将此枚举常量与指定的枚举常量比较序号
。返回负整数、零或正整数,表明此对象小于、等于或大于指定对象。枚举常量仅与相同枚举类型的其他枚举常量相比较
(这个正是泛型参数规定参数类型的具体表现,使得Java API
更加健壮)。此方法比较的序号是声明常量的顺序
。
1 2 3 4 5 6 7 8 public final int compareTo (E o) { Enum<?> other = (Enum<?>)o; Enum<E> self = this ; if (self.getClass() != other.getClass() && self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException (); return self.ordinal - other.ordinal; }
注意一下:compareTo方法里面的条件判断很有讲究,因为枚举类可以引入抽象方法
并声明不同的枚举常量实例实现该方法,因此传入的参数可能是该枚举类的其中一个枚举常量(本质是枚举类的子类
),此时调用getClass比较一定是不相等的,只能进一步调用getDeclaringClass
解决这种情况。
getDeclaringClass
返回与此枚举常量的枚举类型对应的Class对象。两个枚举常量e1和e2属于相同的枚举类型,当且仅当e1.getDeclaringClass()==e2.getDeclaringClass()
。
1 2 3 4 5 public final Class<E> getDeclaringClass () { Class<?> clazz = getClass(); Class<?> zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper; }
下面我们举一个例子论证我们的观点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package test.lang;public enum Operation { PLUS { public double eval (double a, double b) { return a + b; } }, MINUS { public double eval (double a, double b) { return a - b; } }, MULTI { public double eval (double a, double b) { return a * b; } }, DIVIDE { public double eval (double a, double b) { return a / b; } }; public abstract double eval (double a, double b) ; }
valueOf
是Enum类的静态方法,与具体的枚举实例无关,如果眼尖的话可以看到该方法的泛型参数变为T extends Enum<T>
而不是之前的实例方法的E
,因此enumType参数接受任何枚举类型的Class对象,valueOf
方法的作用也就是返回指定枚举类的某个枚举常量(名称为参数name
)。
1 2 3 4 5 6 7 8 9 10 public static <T extends Enum <T>> T valueOf (Class<T> enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null ) return result; if (name == null ) throw new NullPointerException ("Name is null" ); throw new IllegalArgumentException ( "No enum constant " + enumType.getCanonicalName() + "." + name); }
我们再用两行代码验证valueOf
方法的使用:
1 2 Operation plus = Operation.valueOf("PLUS" );System.out.println(plus == Operation.PLUS);
五、Enum类的其他方法
1 2 3 public String toString () { return name; }
以下是Enum类中一些不支持的方法:
1 2 3 4 5 6 7 8 9 10 11 protected final Object clone () throws CloneNotSupportedException { throw new CloneNotSupportedException (); } protected final void finalize () { }private void readObject (ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException ("can't deserialize enum" ); }
六、Enum类的反编译1 首先,我们声明一个Season
枚举类代表一年四季:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package test.lang;public enum Season { SPRING(1 , "春天" ), SUMMER(2 , "夏天" ), AUTUMN(3 , "秋天" ), WINTER(4 , "冬天" ); public int code; public String name; Season(int code, String name) { this .code = code; this .name = name; } public static Season getSeasonByCode (int code) { for (Season season : Season.values()) { if (season.code == code) { return season; } } return null ; } }
然后,我们使用javac
命令编译Season.java
为Season.class
字节码文件,接着使用jad
工具反编译字节码文件得到Season.jad
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package test.decompile;public final class Season extends Enum { public static Season[] values() { return (Season[])$VALUES.clone(); } public static Season valueOf (String name) { return (Season)Enum.valueOf(test/lang/Season, name); } private Season (String s, int i, int code, String name) { super (s, i); this .code = code; this .name = name; } public static Season getSeasonByCode (int code) { Season aseason[] = values(); int i = aseason.length; for (int j = 0 ; j < i; j++) { Season season = aseason[j]; if (season.code == code) return season; } return null ; } public static final Season SPRING; public static final Season SUMMER; public static final Season AUTUMN; public static final Season WINTER; public int code; public String name; private static final Season $VALUES[]; static { SPRING = new Season ("SPRING" , 0 , 1 , "\u6625\u5929" ); SUMMER = new Season ("SUMMER" , 1 , 2 , "\u590F\u5929" ); AUTUMN = new Season ("AUTUMN" , 2 , 3 , "\u79CB\u5929" ); WINTER = new Season ("WINTER" , 3 , 4 , "\u51AC\u5929" ); $VALUES = (new Season [] { SPRING, SUMMER, AUTUMN, WINTER }); } }
Season
在底层其实还是用class
修饰的,说明枚举类本质上还是一个Class
,而且Season被
JVM隐式的用final
关键字修饰起来,那么这个类不能被任何类继承,这也在一定程度上保证了类的安全。
values
方法:默认生成了一个values
方法,它返回了当前类型枚举的数组,这也解释了ordinal
的来源。
valueOf
方法:在具体的每个自定义枚举类里面生成了一个根据枚举名返回枚举实例的方法,这其实是对java.lang.Enum
中valueOf
方法的具体重写,它只限于当前枚举类
。
静态代码块:Season
中所有的enum
实例在这里初始化完成。
注意:当一个Java
类第一次被真正使用到的时候静态资源被初始化、Java
类的加载和初始化过程都是线程安全的。所以创建一个enum
类型是线程安全的。
七、Enum类的反编译2 我们再看一个枚举类型反编译的情况,第一步还是创建Operation
枚举类(我们之前用过):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package test.lang;public enum Operation { PLUS { public double eval (double a, double b) { return a + b; } }, MINUS { public double eval (double a, double b) { return a - b; } }, MULTI { public double eval (double a, double b) { return a * b; } }, DIVIDE { public double eval (double a, double b) { return a / b; } }; public abstract double eval (double a, double b) ; }
然后,我们使用javac
命令编译Operation.java
为Operation.class
字节码文件,接着使用jad
工具反编译字节码文件得到Opeartion.jad
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 package test.lang;public abstract class Operation extends Enum { public static Operation[] values() { return (Operation[])$VALUES.clone(); } public static Operation valueOf (String name) { return (Operation)Enum.valueOf(test/lang/Operation, name); } private Operation (String s, int i) { super (s, i); } public abstract double eval (double d, double d1) ; public static final Operation PLUS; public static final Operation MINUS; public static final Operation MULTI; public static final Operation DIVIDE; private static final Operation $VALUES[]; static { PLUS = new Operation ("PLUS" , 0 ) { public double eval (double a, double b) { return a + b; } }; MINUS = new Operation ("MINUS" , 1 ) { public double eval (double a, double b) { return a - b; } }; MULTI = new Operation ("MULTI" , 2 ) { public double eval (double a, double b) { return a * b; } }; DIVIDE = new Operation ("DIVIDE" , 3 ) { public double eval (double a, double b) { return a / b; } }; $VALUES = (new Operation [] { PLUS, MINUS, MULTI, DIVIDE }); } }
可以看出,枚举类型可以包含抽象方法,声明enum
时不需要加abstract
关键字,Java编译器默认会帮我们完成所有累活,这个例子告诉了我们枚举常量可能是所属枚举类的实现子类,因此进一步论证了我们之前谈到的Enum类的getDeclaringClass
方法。