深度分析 Java 的列舉型別:列舉的執行緒安全性及序列化問題

HollisChuang發表於2015-11-06

寫在前面:

Java SE5 提供了一種新的型別 Java的列舉型別,關鍵字 enum 可以將一組具名的值的有限集合建立為一種新的型別,而這些具名的值可以作為常規的程式元件使用,這是一種非常有用的功能。本文將深入分析列舉的原始碼,看一看列舉是怎麼實現的,是如何保證執行緒安全的,以及為什麼用列舉實現的單例是最佳方式。

列舉是如何保證執行緒安全的

要想看原始碼,首先得有一個類吧,那麼列舉型別到底是什麼類呢?是enum嗎?答案很明顯不是,enum就和class一樣,只是一個關鍵字,他並不是一個類,那麼列舉是由什麼類維護的呢,我們簡單的寫一個列舉:

然後我們使用反編譯,看看這段程式碼到底是怎麼實現的,反編譯(Java的反編譯)後程式碼內容如下:

通過反編譯後程式碼我們可以看到,public final class T extends Enum,說明,該類是繼承了Enum類的,同時final關鍵字告訴我們,這個類也是不能被繼承的。當我們使用enmu來定義一個列舉型別的時候,編譯器會自動幫我們建立一個final型別的類繼承Enum類,所以列舉型別不能被繼承,我們看到這個類中有幾個屬性和方法。

我們可以看到:

都是static型別的,因為static型別的屬性會在類被載入之後被初始化,我們在深度分析Java的ClassLoader機制(原始碼級別)Java類的載入、連結和初始化兩個文章中分別介紹過,當一個Java類第一次被真正使用到的時候靜態資源被初始化、Java類的載入和初始化過程都是執行緒安全的。所以,建立一個enum型別是執行緒安全的

為什麼用列舉實現的單例是最好的方式

[轉+注]單例模式的七種寫法中,我們看到一共有七種實現單例的方式,其中,Effective Java作者Josh Bloch 提倡使用列舉的方式,既然大神說這種方式好,那我們就要知道它為什麼好?

1. 列舉寫法簡單

寫法簡單這個大家看看[轉+注]單例模式的七種寫法裡面的實現就知道區別了。

你可以通過EasySingleton.INSTANCE來訪問。

2. 列舉自己處理序列化

我們知道,以前的所有的單例模式都有一個比較大的問題,就是一旦實現了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方法:

從程式碼中可以看到,程式碼會嘗試從呼叫enumType這個Class物件的enumConstantDirectory()方法返回的map中獲取名字為name的列舉物件,如果不存在就會丟擲異常。再進一步跟到enumConstantDirectory()方法,就會發現到最後會以反射的方式呼叫enumType這個型別的values()靜態方法,也就是上面我們看到的編譯器為我們建立的那個方法,然後用返回結果填充enumType這個Class物件中的enumConstantDirectory屬性。

所以,JVM對序列化有保證。

3.列舉例項建立是thread-safe(執行緒安全的)

我們在深度分析Java的ClassLoader機制(原始碼級別)Java類的載入、連結和初始化兩個文章中分別介紹過,當一個Java類第一次被真正使用到的時候靜態資源被初始化、Java類的載入和初始化過程都是執行緒安全的。所以,建立一個enum型別是執行緒安全的

相關文章