Inside java.lang.Enum


    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)静态方法构造枚举值,从而保证了枚举值的单例性。
 

你可能感兴趣的:(java)