枚举类型

定义

枚举类型(enum type)是指由一组固定的常量组成合法的类型。Java 中由关键字 enum 来定义一个枚举类型。

1
2
3
public enum Season {
SPRING, SUMMER, AUTUMN, WINER;
}

语句定义:

  1. 使用关键字 enum
  2. 类型名称,比如这里的Season
  3. 枚举中的属性必须放在最前面,一般使用大写字母表示
  4. 枚举可以单独定义在一个文件中,也可以嵌在其它Java类中
  5. 枚举可以实现一个或多个接口(Interface)
  6. 可以和 java 类一样定义方法 ,枚举中的构造方法必须是私有的。

实现

使用 javap 进行反编译可以看到 public final class Season extends Enum,也就是说当我们使用 enum 来定义一个枚举类型的时候,编译器会自动帮我们创建一个 final 类型且继承自 Enum 的类。

1
2
3
4
5
6
7
8
9
public final class Season extends java.lang.Enum<Season> {
public static final com.muhouer.algorithm.Season SPRING;
public static final com.muhouer.algorithm.Season SUMMER;
public static final com.muhouer.algorithm.Season AUTUMN;
public static final com.muhouer.algorithm.Season WINER;
public static com.muhouer.algorithm.Season[] values();
public static com.muhouer.algorithm.Season valueOf(java.lang.String);
static {};
}

枚举类的字段也可以是非 final 类型,即可以在运行期修改,但是不推荐这样做!默认情况下,对枚举常量调用 toString() 会返回和 name() 一样的字符串。但是,toString() 可以被覆写,而 name() 则不行。

一些特殊方法

  1. Java 枚举值比较用 ==equals() 方法效果是一样的。因为枚举 Enum 类的 equals() 方法的实现就是使用 ==

  2. Enum 的 compareTo()方法实际上比较的是 Enum 的 ordinal 顺序大小;

  3. Enum 在 switch 中比较的是 Enum 的 ordinal 值;

  4. Enum 的 name()方法和 toString() 方法效果一样,返回的都是 Enum 的 name 值。

序列化及线程安全

为了保证枚举类型像 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() 方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了 writeObjectreadObjectreadObjectNoDatawriteReplacereadResolve 等方法。 我们看一下这个valueOf()方法:

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
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);
}
private volatile transient Map<String, T> enumConstantDirectory = null;
/**
* Returns a map from simple name to enum constant. This package-private
* method is used internally by Enum to implement
* {@code public static <T extends Enum<T>> T valueOf(Class<T>, String)}
* efficiently. Note that the map is returned by this method is
* created lazily on first use. Typically it won't ever get created.
*/
Map<String, T> enumConstantDirectory() {
if (enumConstantDirectory == null) {
T[] universe = getEnumConstantsShared(); // 反射的方式调用枚举类型的 values() 静态方法
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
enumConstantDirectory = m;
}
return enumConstantDirectory;
}

从👆可以看出,JVM 对枚举类型序列化有保证。

枚举类型是一个 final 类型的继承自 Enum 的类,所以枚举类型不能被继承,而且这个类中的属性和方法都是static 类型的。当一个 Java 类第一次被真正使用到的时候静态资源初始化、Java 类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的

单例

在StakcOverflow中,有一个关于在 Java 中哪种写单例的方式最好的讨论。

Joshua Bloch大神在《Effective Java》中明确表达过的观点:

使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。

使用枚举实现单例的好处:

  1. 枚举单例写法简单。

    1
    2
    3
    4
    5
    public enum Singleton {  
    INSTANCE;
    public Connection getConnection() {
    }
    }
  2. 线程安全:枚举实现的单例是天生线程安全的。

  3. 反序列化不会破坏单例。

    普通的 Java 类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新 new 出来的,所以这就破坏了单例。