1 Introduction to enum
Java SE 5中引入了枚举,同时添加了一个新关键字enum。下面是个枚举的例子:
public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES; }
枚举类型也是普通的Java类,继承自java.lang.Enum并默认实现了java.lang.Comparable接口和java.io.Serializable接口。所有的枚举类型都是final类,枚举值都是public static final,由于枚举值是常量,因此枚举值的名称通常应该大写。
枚举类型也可以声明构造函数(只能是私有或者包级私有)、成员变量和成员方法,此外也能实现接口。需要注意的是,成员变量和成员方法的声明必须放在所有枚举值定义的后面,例如:
public enum Status { // Normal("normal"), Warning("warning"), Error("error"), Fatal("fatal"); // private String description; private Status(String description) { this.description = description; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
Enum声明了name()方法和oridinal()方法,分别用于返回枚举值的名称和该枚举值在枚举类型中声明的顺序(从0开始),例如NORMAL.ordinal()返回0。Enum改写了toString方法,返回枚举值的名称。下面是个简单的例子:
public static void main(String args[]) { // System.out.println("super class: " + Status.class.getSuperclass()); // System.out.println(Status.valueOf("Normal").getDescription()); // for(Status s: Status.values()) { System.out.println(s + ":" + s.ordinal()); } // Status status = Status.Normal; switch(status) { case Normal: System.out.println("Status.Normal"); break; case Warning: System.out.println("Status.Warning"); break; case Error: System.out.println("Status.Error"); break; case Fatal: System.out.println("Status.Fatal"); break; default: System.out.println("unknown"); break; } // EnumMap<Status, String> map = new EnumMap<Status, String>(Status.class); map.put(Status.Normal, "normal status"); map.put(Status.Warning, "warning status"); map.put(Status.Error, "error status"); map.put(Status.Fatal, "fatal status"); // EnumSet<Status> set = EnumSet.of(Status.Normal, Status.Warning); for(Status s : set) { System.out.println(s); } }
以上程序的输出如下。需要注意的是,case语句只需将其写成 case Normal 即可,也就是说不必写成 case Status.Normal,实际上如果写成Status.Normal,那么会导致编译错误。
super class: class java.lang.Enum normal Normal:0 Warning:1 Error:2 Fatal:3 Status.Normal Normal Warning
2 Inside java.lang.Enum
2.1 Instantiation
首先java.lang.Enum类的签名如下,其中<E extends Enum<E>>是递归类型限制,主要目的是提供compareTo(E e) 而不是compareTo(Enum e)。
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable
java.lang.Enum中唯一的构造函数如下,所有的成员变量都在构造函数内初始化。由于所有的成员变量都是不可变类型或者基本类型,因此没有额外的保护性拷贝。构造之后,所有的成员变量便处于只读状态。需要注意的是,java.lang.Enum的子类不一定是不可变类(虽然通常应该是不可变类),因为程序中定义的枚举类型可以包含可变的成员变量。
protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }
java.lang.Enum类提供了一个静态工具方法,用于获得某个枚举类型中,名为name的枚举值。该方法通过调用java.lang.Class的 enumConstantDirectory()方法获得该枚举类型所有枚举值的map。java.lang.Class的enumConstantDirectory()方法内又通过反射调用了该枚举类型的values()方法获得所有的枚举值。需要注意的是,java.lang.Enum中并没有一个名为values()的静态方法,这个方法是编译器在编译枚举类型时添加的。
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 const " + enumType +"." + name); }
2.2 Equality
public final boolean equals(Object other) { return this==other; } public final int hashCode() { return super.hashCode(); }
从以上代码中可以看出,对于枚举值的相等性判断,只需要判断引用是否相等即可。需要注意的是,这是在充分考虑了反射、对象克隆和序列化等诸多因素之后作出的决定。
java.lang.reflect.Constructor的newInstance()方法中有如下代码,禁止了通过反射构造枚举对象:
if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects");
2.3 Comparison
以下是跟枚举比较相关的代码:
public final Class<E> getDeclaringClass() { Class clazz = getClass(); Class zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? clazz : zuper; } public final int compareTo(E o) { Enum other = (Enum)o; Enum self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; }
假设程序中定义的所有枚举类型都是继承自java.lang.Enum,而且所有的枚举类型都被声明为final类,这是否意味着所有枚举值的super class都应该是java.lang.Enum?为什么在getDeclaringClass()方法内会有对super class的判断呢?分析以下例子:
public enum Operation { PLUS { double eval(double x, double y) { return x + y; } }, MINUS { double eval(double x, double y) { return x - y; } }, TIMES { double eval(double x, double y) { return x * y; } }, DIVIDE { double eval(double x, double y) { return x / y; } }; abstract double eval(double x, double y); public static void main(String args[]) { System.out.println(Operation.TIMES.getClass()); System.out.println(Operation.TIMES.getClass().getSuperclass()); System.out.println(Operation.TIMES.getDeclaringClass()); } }
以上程序的输出如下:
class Operation$3 class Operation class Operation
从这个例子可以看出,并不是所有的枚举值的super class都是Emum.class。在compareTo()方法内需要判断getDeclaringClass()和ordinal是否相等。就像其注释中说明的那样,判断getClass()是否相等只是一种优化。
此外,java.lang.Class类中包含以下代码,用于判断一个类是否是枚举类型。需要注意的是,这里没有使用getDeclaringClass(),而是直接使用getSuperclass()进行判断。
public boolean isEnum() { // An enum must both directly extend java.lang.Enum and have // the ENUM bit set; classes for specialized enum constants // don't do the former. return (this.getModifiers() & ENUM) != 0 && this.getSuperclass() == java.lang.Enum.class; }
2.4 Clone
以下是java.lang.Enum类的clone()方法,该方法中直接抛出CloneNotSupportedException异常,这保证了java.lang.Enum无法被克隆,从而保证了枚举值的单例性(通常情况下,只有一个继承链上的除了java.lang.Object之外的所有类都在clone()方法内返回通过调用super.clone()方法返回的对象,那么才能保证clone的正确性)。
protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); }
2.5 Serialization
java.lang.Enum类中序列化相关的代码如下:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("can't deserialize enum"); } private void readObjectNoData() throws ObjectStreamException { throw new InvalidObjectException("can't deserialize enum"); }
java.lang.Enum类实现了Serializable接口,但是却在readObject()和readObjectNoData()方法内直接抛出异常(如果某个类因为继承的原因实现了Serializable接口,而该类却不希望被序列化/反序列化,那么通常可以考虑在readObject()和writeObject()方法中直接抛出异常)。
实际上,Java的序列化机制对于枚举类型有特殊的处理,即没有使用普通对象的序列化形式:尽管java.lang.Enum中的name和ordial成员变量都没有声明为transient,实际上序列化过程中写入流的只有name;反序列化过程中通过调用Enum.valueOf(Class<T> enumType, String name)静态方法构造枚举值,从而保证了枚举值的单例性。