Java列舉類之Enum原始碼分析
阿新 • • 發佈:2022-05-23
一、概述
列舉型別是 JDK 5 之後引進的一種非常重要的引用型別,可以用來定義一系列列舉常量。
在沒有引入 enum 關鍵字之前,要表示可列舉的變數,只能使用 public static final 的方式。
public staic final int SPRING = 1;
public staic final int SUMMER = 2;
public staic final int AUTUMN = 3;
public staic final int WINTER = 4;
這種實現方式有幾個弊端。首先,型別不安全。試想一下,有一個方法期待接受一個季節作為引數,那麼只能將引數型別宣告為 int,但是傳入的值可能是99。顯然只能在執行時進行引數合理性的判斷,無法在編譯期間完成檢查。其次,指意性不強,含義不明確。我們使用列舉,很多場合會用到該列舉的字串符表達,而上述的實現中只能得到一個數字,不能直觀地表達該列舉常量的含義。當然也可用 String 常量,但是又會帶來效能問題,因為比較要依賴字串的比較操作。
使用enum來表示列舉可以更好地保證程式的型別安全和可讀性。
enum是型別安全的。除了預先定義的列舉常量,不能將其它的值賦給列舉變數。這和用 int 或 String 實現的列舉很不一樣。
enum有自己的名稱空間,且可讀性強。在建立enum時,編譯器會自動新增一些有用的特性。每個enum 例項都有一個名字 (name) 和一個序號 (ordinal),可以通過 toString() 方法獲取 enum 例項的字串表示。還以通過 values() 方法獲得一個由 enum 常量按順序構成的陣列。
enum還有一個特別實用的特性,可以在switch語句中使用,這也是enum最常用的使用方式了。
二、原始碼分析
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { //列舉常量的名稱 //大多數程式設計師應該使用toString方法而不是訪問此欄位。 private final String name; //返回此列舉常量的名稱,與其列舉宣告中宣告的完全相同 public final String name() { return name; } //此列舉常量的序數(它在列舉宣告中的位置,其中初始常量的序數為零) 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; } //果指定的物件等於此列舉常量,則返回true。 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(); } //將此列舉與指定的列舉序號進行比較 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; } //返回與此列舉常量的列舉型別相對應的Class物件 @SuppressWarnings("unchecked") public final Class<E> getDeclaringClass() { Class<?> clazz = getClass(); Class<?> zuper = clazz.getSuperclass(); return (zuper == java.lang.Enum.class) ? (Class<E>)clazz : (Class<E>)zuper; } /** * 返回具有指定名稱的指定列舉型別的列舉常量。 * 該名稱必須與用於宣告此型別的列舉常量的識別符號完全一致。 * 請注意,對於特定列舉型別T , * 有兩個隱式宣告方法可以直接使用: * public static T valueOf(String) 根據名稱獲取單個列舉型別 * public static T[] 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 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"); } }
案例
例1
@Getter
public enum DelFlagEnum {
NO(0, "未刪除"),
YES(1, "已刪除"),;
Integer code;
String name;
DelFlagEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
public static DelFlagEnum getCode(Integer code) {
for (DelFlagEnum value : DelFlagEnum.values()) {
if(value.getCode().equals(code)) {
return value;
}
}
return null;
}
}
例2
@Getter
public enum InsuranceIndustry {
INSURANCE_AGENT("1010", "保險代理"),
INSURANCE_BROKER("1011", "保險經紀"),
INSURANCE_ADJUSTMENT("1012", "保險公估"),
PROPERTY_INSURANCE("1013", "財險公司"),
LIFE_INSURANCE("1014", "壽險公司"),
HEALTH_INSURANCE("1015", "健康保險"),
ENDOWMENT_INSURANCE("1016", "養老保險"),
;
String code;
String name;
InsuranceIndustry(String code, String name) {
this.code = code;
this.name = name;
}
// 將資料快取到map中
private static final Map<String, String> map = new HashMap<>();
static {
for (InsuranceIndustry insurance : InsuranceIndustry.values()) {
map.put(insurance.getCode(), insurance.getName());
}
}
// 根據name查詢value值
public static String getNameByCode(String code) {
return map.get(code);
}
}
拓展
1. 列舉是如何保證執行緒安全的
當一個Java類第一次被真正使用到的時候靜態資源被初始化、Java類的載入和初始化過程都是執行緒安全的。所以,建立一個enum型別是執行緒安全的。
2. 為什麼用列舉實現的單例是最好的方式
- 列舉寫法簡單
- 列舉自己處理序列化
- 列舉例項建立是thread-safe(執行緒安全的)