前言
對於比較穩定的值集合,Java 提供了列舉來定義,通過它可以很方便管理集合。那麼 Java 的列舉是通過怎樣的機制實現的?本文將從 JDK 角度來看看列舉的原理。
定義列舉
使用很簡單,比如定義一個表示“環保”、“交通”、“手機”三個值的集合,那麼就可以直接定義如下,然後可直接 Labels.ENVIRONMENT 使用,
public enum Labels {
ENVIRONMENT(), TRAFFIC(), PHONE();
}
複製程式碼
同時也可以使用帶建構函式的列舉,如下,可以通過 getName 獲取值。
public enum Labels0 {
ENVIRONMENT("環保"), TRAFFIC("交通"), PHONE("手機");
private String name;
private Labels0(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
複製程式碼
編譯器做了什麼
Java中的列舉的實現機制是怎樣的?列舉看起來有點像上帝扔給我們的語法糖,秉著深入挖一挖的精神,看看列舉是相關實現,看看編譯器做了什麼。用 javap 看上面兩個列舉編譯後的位元組碼:
public final class com.seaboat.Labels extends java.lang.Enum<com.seaboat.Labels> {
public static final com.seaboat.Labels ENVIRONMENT;
public static final com.seaboat.Labels TRAFFIC;
public static final com.seaboat.Labels PHONE;
static {};
public static com.seaboat.Labels[] values();
public static com.seaboat.Labels valueOf(java.lang.String);
}
複製程式碼
public final class com.seaboat.Labels0 extends java.lang.Enum<com.seaboat.Labels0> {
public static final com.seaboat.Labels0 ENVIRONMENT;
public static final com.seaboat.Labels0 TRAFFIC;
public static final com.seaboat.Labels0 PHONE;
static {};
public java.lang.String getName();
public static com.seaboat.Labels0[] values();
public static com.seaboat.Labels0 valueOf(java.lang.String);
}
複製程式碼
可以清晰地看到列舉被編譯後其實就是一個類,該類被宣告成 final,說明其不能被繼承,同時它繼承了 Enum 類。列舉裡面的元素被宣告成 static final ,另外生成一個靜態程式碼塊 static{},最後還會生成 values 和 valueOf 兩個方法。下面以最簡單的 Labels 為例,一個一個模組來看。
Enum 類
Enum 類是一個抽象類,主要有 name 和 ordinal 兩個屬性,分別用於表示列舉元素的名稱和列舉元素的位置索引,而建構函式傳入的兩個變數剛好與之對應。
- toString 方法直接返回 name。
- equals 方法直接用 == 比較兩個物件。
- hashCode 方法呼叫的是父類的 hashCode 方法。
- 列舉不支援 clone、finalize 和 readObject 方法。
- compareTo 方法可以看到就是比較 ordinal 的大小。
- valueOf 方法,根據傳入的字串 name 來返回對應的列舉元素。
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
private final int ordinal;
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;
}
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() &&
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;
}
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);
}
@SuppressWarnings("deprecation")
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");
}
}
複製程式碼
靜態程式碼塊
可以看到靜態程式碼塊主要完成的工作就是先分別建立 Labels 物件,然後將“ENVIRONMENT”、“TRAFFIC”和“PHONE”字串作為 name ,按照順序分別分配位置索引0、1、2作為 ordinal,然後將其值設定給建立的三個 Labels 物件的 name 和 ordinal 屬性,此外還會建立一個大小為3的 Labels 陣列 ENUM$VALUES,將前面建立出來的 Labels 物件分別賦值給陣列。
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #1 // class com/seaboat/Labels
3: dup
4: ldc #14 // String ENVIRONMENT
6: iconst_0
7: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #19 // Field ENVIRONMENT:Lcom/seaboat/Labels;
13: new #1 // class com/seaboat/Labels
16: dup
17: ldc #21 // String TRAFFIC
19: iconst_1
20: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #22 // Field TRAFFIC:Lcom/seaboat/Labels;
26: new #1 // class com/seaboat/Labels
29: dup
30: ldc #24 // String PHONE
32: iconst_2
33: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
36: putstatic #25 // Field PHONE:Lcom/seaboat/Labels;
39: iconst_3
40: anewarray #1 // class com/seaboat/Labels
43: dup
44: iconst_0
45: getstatic #19 // Field ENVIRONMENT:Lcom/seaboat/Labels;
48: aastore
49: dup
50: iconst_1
51: getstatic #22 // Field TRAFFIC:Lcom/seaboat/Labels;
54: aastore
55: dup
56: iconst_2
57: getstatic #25 // Field PHONE:Lcom/seaboat/Labels;
60: aastore
61: putstatic #27 // Field ENUM$VALUES:[Lcom/seaboat/Labels;
64: return
LineNumberTable:
line 5: 0
line 3: 39
LocalVariableTable:
Start Length Slot Name Signature
複製程式碼
values 方法
可以看到它是一個靜態方法,主要是使用了前面靜態程式碼塊中的 Labels 陣列 ENUM$VALUES,呼叫 System.arraycopy 對其進行復制,然後返回該陣列。所以通過 Labels.values()[2]
就能獲取到陣列中索引為2的元素。
public static com.seaboat.Labels[] values();
descriptor: ()[Lcom/seaboat/Labels;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=5, locals=3, args_size=0
0: getstatic #27 // Field ENUM$VALUES:[Lcom/seaboat/Labels;
3: dup
4: astore_0
5: iconst_0
6: aload_0
7: arraylength
8: dup
9: istore_1
10: anewarray #1 // class com/seaboat/Labels
13: dup
14: astore_2
15: iconst_0
16: iload_1
17: invokestatic #35 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
20: aload_2
21: areturn
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
複製程式碼
valueOf 方法
該方法同樣是個靜態方法,可以看到該方法的實現是間接呼叫了父類 Enum 類的 valueOf 方法,根據傳入的字串 name 來返回對應的列舉元素,比如可以通過 Labels.valueOf("ENVIRONMENT")
獲取 Labels.ENVIRONMENT
。
public static com.seaboat.Labels valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)Lcom/seaboat/Labels;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #1 // class com/seaboat/Labels
2: aload_0
3: invokestatic #43 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #1 // class com/seaboat/Labels
9: areturn
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
複製程式碼
總結
列舉本質其實也是一個類,而且都會繼承java.lang.Enum類,同時還會生成一個靜態程式碼塊 static{},並且還會生成 values 和 valueOf 兩個方法。而上述的工作都需要由編譯器來完成,然後我們就可以像使用我們熟悉的類那樣去使用列舉了。
————-推薦閱讀————
——————廣告時間—————-
公眾號的選單已分為“分散式”、“機器學習”、“深度學習”、“NLP”、“Java深度”、“Java併發核心”、“JDK原始碼”、“Tomcat核心”等,可能有一款適合你的胃口。
鄙人的新書《Tomcat核心設計剖析》已經在京東銷售了,有需要的朋友可以購買。感謝各位朋友。
歡迎關注: