【Java基礎】關於列舉類你可能不知道的事
目錄
- 談談列舉
- 1. 列舉類的定義
- 2. 列舉類的底層實現
- 3. 列舉類的序列化實現
- 4. 用列舉實現單列
- 5. 列舉例項的建立過程是執行緒安全的
談談列舉
如果一個類的物件個數是有限的而且是不變的,我們通常將這樣的類設計成列舉類。
1. 列舉類的定義
列舉類有如下特點:
- 列舉類預設是使用final關鍵字修飾的,所以列舉類不能被繼承;
- 列舉類的建構函式預設是使用private修飾的;
- 定義列舉類時所有例項必須在第一行全部列出;
- 列舉類也可以實現介面;
- 列舉類可以包含抽象方法。
//預設final修飾,不能被繼承 public enum EnumDemo implements Runnable { //列舉的欄位必須加註釋 //男性 MALE("male"){ @Override //這邊每個列舉類都單獨實現了介面方法,也可以統一實現 //在列舉類的定義中實現一個就好了 public void run() { System.out.println("l like run..."); } @Override public void tellSex() { System.out.println("l am a man"); } }, //女性 Female("female"){ @Override public void run() { System.out.println("l hate running..."); } @Override public void tellSex() { System.out.println("l am a girl"); } }; private String sex; public String getSex(){ return sex; } /** * 建構函式預設是priva的 * @param sex */ EnumDemo(String sex){ this.sex = sex; } /** * 抽象方法,需要列舉類例項實現這個方法 */ public abstract void tellSex(); }
2. 列舉類的底層實現
假如有如下的一個列舉類定義
public enum T {
SPRING,SUMMER,AUTUMN,WINTER;
}
經過Java編譯器編譯後,如果我們反編譯class檔案可以看到如下程式碼:
public final class T extends Enum { private T(String s, int i) { super(s, i); } public static T[] values() { T at[]; int i; T at1[]; System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i); return at1; } public static T valueOf(String s) { return (T)Enum.valueOf(demo/T, s); } public static final T SPRING; public static final T SUMMER; public static final T AUTUMN; public static final T WINTER; private static final T ENUM$VALUES[]; static { SPRING = new T("SPRING", 0); SUMMER = new T("SUMMER", 1); AUTUMN = new T("AUTUMN", 2); WINTER = new T("WINTER", 3); ENUM$VALUES = (new T[] { SPRING, SUMMER, AUTUMN, WINTER }); } }
可以看到經過編譯後,列舉類也是一個普通的Java類。這個類繼承了Enum這個類,並且使用final修飾,所以列舉類都是不能被繼承的。列舉類中定義的列舉值都是這個列舉類的靜態成員變數,而且在初始化程式碼塊中會一次性初始化,放在value陣列中。我們呼叫列舉類的values()方法能一次性拿到所有的列舉值。關於列舉類,有幾個重要的方法需要說下。從上面的程式碼可以看出我們定義的列舉類都會繼承Enum這個類。
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
//列舉的名字,我們可以通valueof(name)來獲取列舉值
//這個就是指代我們定義的列舉例項的名字,比如上面的“SPRING”和“SUMMER”等
private final String name;
public final String name() {
return name;
}
//列舉的大小,列舉值比較大小預設的就是比較這個值的大小
//這個值的大小是根據我們定義列舉值的順序來的,比如一個
//列舉值我們第一個定義那麼這個列舉的ordinal就是0,比如上面定義的“SPRING”的ordinal值就是0
// 上面定義的“SUMMER”的值就是1,還有一點需要說明的就是:當我們在switch中使用列舉型別時,
//編譯後就是匹配的這個ordinal值
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public String toString() {
return name;
}
public final boolean equals(Object other) {
return this==other;
}
public final int hashCode() {
return super.hashCode();
}
//禁止克隆
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
//比較ordinal的大小
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
//通過name來獲得列舉
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);
}
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");
}
}
3. 列舉類的序列化實現
以前的所有的單例模式都有一個比較大的問題,就是一旦實現了Serializable介面之後,就不再是單例了,因為,每次呼叫 readObject()方法返回的都是一個新創建出來的物件,有一種解決辦法就是使用readResolve()方法來避免此事發生。但是,為了保證列舉型別像Java規範中所說的那樣,每一個列舉型別極其定義的列舉變數在JVM中都是唯一的,在列舉型別的序列化和反序列化上,Java做了特殊的規定,即在序列化的時候Java僅僅是將列舉物件的name屬性輸出到結果中,反序列化的時候則是通過java.lang.Enum的valueOf方法來根據名字查詢列舉物件。同時,編譯器是不允許任何對這種序列化機制的定製的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
//Quarter是一個列舉類
Quarter[] values = Quarter.values();
Quarter one = values[0];
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\enum.txt"));
objectOutputStream.writeObject(one);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\enum.txt"));
Object object = objectInputStream.readObject();
//返回true
System.out.println(object==one);
列舉類在序列化的時候只會將name屬相序列化。在上面程式碼中ObjectInputStream類的readObject方法進行反序列化時會先判斷被反序列化的類的型別,如果是列舉類就獲取這個列舉類的型別再呼叫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 constant " + enumType.getCanonicalName() + "." + name);
}
通過上面的程式碼可以看出,valueof方法根據獲得的類還是列舉類初始化時快取起來的類。所以系統中還是隻會存在一個列舉類。
4. 用列舉實現單列
普通的單列實現方式有一個比較大的問題就是如果將單列類序列化後再進行反序列化那麼同一個jvm中將存在兩個單列類。通過上面的分析列舉類的反序列化不會出現這個問題,所以通過列舉類來實現單例模式是一個很好的選擇。
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
5. 列舉例項的建立過程是執行緒安全的
從第二部分的程式碼我們可以看出,列舉例項的建立是在靜態程式碼塊中建立的。靜態程式碼塊會在類的初始化過程中執行,而類的初始化過程是執行緒安全的,所以列舉例項的建立過程是執行緒安全