Java 枚举在JDK1.5版本中被引入,枚举用于将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。枚举是一种非常有用的功能。
写一个简单的颜色枚举,代码如下:
Color.java
public enum Color{
RED,GREEN,BLUE
}
编译Color.java 得到 Color.class,利用Java 反编译 命令javap Color.class
得到如下结果:
从上图中可以看到,实际上创建一个enum时,编译器会为你生成一个相关的类,这个类继承自java.lang.Enum
,并且这是个final类,意味着这个类不能被继承。我们转到Enum类去看下它的实现。
以下源码来自Sun JDK 1.7中的Enum类。
package java.lang;
import java.io.Serializable;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
/** * 这是所有 Java 语言枚举类型的公共基本类 */
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
//枚举常量的名称
private final String name;
//返回此枚举常量的名称,在其枚举声明中对其进行声明。
public final String name() {
return name;
}
//返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)
public final int ordinal() {
return ordinal;
}
//单独的构造方法。程序员无法调用此构造方法。该构造方法用于由响应枚举类型声明的编译器发出的代码。
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
//返回枚举常量的名称,它包含在声明中。可以重写此方法,虽然一般来说没有必要。当存在更加“程序员友好的”字符串形式时,应该使用枚举类型重写此方法。
public String toString() {
return name;
}
//当指定对象等于此枚举常量时,返回 true。
public final boolean equals(Object other) {
return this==other;
}
//返回枚举常量的哈希码。
public final int hashCode() {
return super.hashCode();
}
//抛出 CloneNotSupportedException。这可保证永远不会复制枚举,这对于保留其“单元素”状态是必需的。
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
//比较此枚举与指定对象的顺序。在该对象小于、等于或大于指定对象时,分别返回负整数、零或正整数。 枚举常量只能与相同枚举类型的其他枚举常量进行比较。该方法实现的自然顺序就是声明常量的顺序。
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;
}
//返回与此枚举常量的枚举类型相对应的 Class 对象。当且仅当 e1.getDeclaringClass() == e2.getDeclaringClass() 时,两个枚举常量 e1 和 e2 的枚举类型才相同。
public final Class<E> getDeclaringClass() {
Class clazz = getClass();
Class zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? clazz : zuper;
}
/** *返回带指定名称的指定枚举类型的枚举常量。名称必须与在此类型中声明枚举常量所用的标识符完全匹配。(不允许使用额外的空白字符。) */
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);
}
/** * 枚举类不能有 finalize 方法。 */
protected final void finalize() { }
/** * 阻止默认的反序列 */
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");
}
}
我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不能保证单例,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。原文如下:
Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant’s name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant’s enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored–all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.
大概意思就是说,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 我们看一下这个valueOf方法:
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);
}
从代码中可以看到,代码会尝试从调用enumType这个Class对象的enumConstantDirectory()方法返回的map中获取名字为name的枚举对象,如果不存在就会抛出异常。
再进一步跟到enumConstantDirectory()方法,
Map<String, T> enumConstantDirectory() {
if (enumConstantDirectory == null) {
T[] universe = getEnumConstantsShared();
if (universe == null)
throw new IllegalArgumentException(
getName() + " is not an enum type");
Map<String, T> m = new HashMap<>(2 * universe.length);
for (T constant : universe)
m.put(((Enum<?>)constant).name(), constant);
enumConstantDirectory = m;
}
return enumConstantDirectory;
}
来看Class.java 中getEnumConstantsShared方法的实现。
T[] getEnumConstantsShared() {
if (enumConstants == null) {
if (!isEnum()) return null;
try {
final Method values = getMethod("values");
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
values.setAccessible(true);
return null;
}
});
enumConstants = (T[])values.invoke(null);
}
// These can happen when users concoct enum-like classes
// that don't comply with the enum spec.
catch (InvocationTargetException ex) { return null; }
catch (NoSuchMethodException ex) { return null; }
catch (IllegalAccessException ex) { return null; }
}
return enumConstants;
}
就会发现到最后会以反射的方式调用enumType这个类型的values()静态方法,也就是上面我们看到的编译器为我们创建的那个方法,然后用返回结果填充enumType这个Class对象中的enumConstantDirectory属性。所以,JVM对序列化有保证。
当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。在上述Color的枚举中,通过反编译得到Color的实现,所有的枚举值均被public static final 修饰符修饰 ,表示枚举值均是静态不可变的,自然也就不会出现线程安全问题。
所以,创建一个enum类型是线程安全的。