我需要將一個有序的int
值轉換爲Java中的枚舉值。這很簡單:Enum#values()是否在每次調用時分配內存?
MyEnumType value = MyEnumType.values()[ordinal];
的values()
方法是隱含的,我無法找到它的源代碼,因此問題。
MyEnumType.values()
是否分配新的數組?如果是這樣,我應該在第一次調用時緩存數組嗎?假設轉換經常被調用。
我需要將一個有序的int
值轉換爲Java中的枚舉值。這很簡單:Enum#values()是否在每次調用時分配內存?
MyEnumType value = MyEnumType.values()[ordinal];
的values()
方法是隱含的,我無法找到它的源代碼,因此問題。
MyEnumType.values()
是否分配新的數組?如果是這樣,我應該在第一次調用時緩存數組嗎?假設轉換經常被調用。
是的,MyEnumType.values()
每次創建新的數組,其中充滿了枚舉元素。您只需使用==
運算符即可對其進行測試。
MyEnumType[] arr1 = MyEnumType.values();
MyEnumType[] arr2 = MyEnumType.values();
System.out.println(arr1==arr2); //false
Java沒有讓我們創建不可修改數組的機制。因此,如果values()
將始終返回相同的數組實例,那麼我們可能會冒有人可以爲每個人更改其內容的風險。
所以直到Java中引入不可修改的數組機制,爲了避免數組可變性問題values()
方法必須創建並返回原始數組的拷貝。
如果你想避免重新創建這個數組,你可以簡單地存儲它,並在以後重用values()
的結果。有幾種方法可以做到這一點,比如。
您可以創建私有數組,只允許通過getter方法訪問其內容,如
private static final MyEnumType[] VALUES = values();// to avoid recreating array
MyEnumType getByOrdinal(int){
return VALUES[int];
}
你可以修改的Collection像List
的values()
也將結果保存,以確保其內容不會被改變(現在這樣的列表可以是公開的)。
public static final List<X> VALUES = Collections.unmodifiableList(Arrays.asList(values()));
從理論上講,values()
方法必須每次都返回一個新的數組,因爲Java沒有一成不變的陣列。如果它總是返回相同的數組,則不能通過修改數組來防止調用者混淆彼此。
我無法定位的源代碼其
的values()
方法沒有普通的源代碼,作爲編譯器生成。對於javac,生成values()
方法的代碼位於com.sun.tools.javac.comp.Lower.visitEnumDef中。對於ECJ(Eclipse的編譯器),代碼位於 org.eclipse.jdt.internal.compiler.codegen.CodeStream.generateSyntheticBodyForEnumValues。
查找values()
方法實現的更簡單方法是分解編譯的枚舉。首先,創建一些無聊的枚舉:
enum MyEnumType {
A, B, C;
public static void main(String[] args) {
System.out.println(values()[0]);
}
}
然後編譯它,並使用包含在JDK javap的工具拆解:
javac MyEnumType.java && javap -c -p MyEnumType
可見的輸出是所有的編譯器生成的隱式成員枚舉,包括(1)每個枚舉常量的一個static final
字段,(2)包含所有常量的隱藏的$VALUES
數組,(3)一個靜態初始化塊,用於實例化每個常量並將其分配給其命名字段和數組,和(4)通過呼叫.clone()
工作的values()
方法o n個$VALUES
陣列和返回結果:
final class MyEnumType extends java.lang.Enum<MyEnumType> {
public static final MyEnumType A;
public static final MyEnumType B;
public static final MyEnumType C;
private static final MyEnumType[] $VALUES;
public static MyEnumType[] values();
Code:
0: getstatic #1 // Field $VALUES:[LMyEnumType;
3: invokevirtual #2 // Method "[LMyEnumType;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[LMyEnumType;"
9: areturn
public static MyEnumType valueOf(java.lang.String);
Code:
0: ldc #4 // class MyEnumType
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class MyEnumType
9: areturn
private MyEnumType(java.lang.String, int);
Code:
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #6 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
6: return
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #8 // Method values:()[LMyEnumType;
6: iconst_0
7: aaload
8: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
11: return
static {};
Code:
0: new #4 // class MyEnumType
3: dup
4: ldc #10 // String A
6: iconst_0
7: invokespecial #11 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #12 // Field A:LMyEnumType;
13: new #4 // class MyEnumType
16: dup
17: ldc #13 // String B
19: iconst_1
20: invokespecial #11 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #14 // Field B:LMyEnumType;
26: new #4 // class MyEnumType
29: dup
30: ldc #15 // String C
32: iconst_2
33: invokespecial #11 // Method "<init>":(Ljava/lang/String;I)V
36: putstatic #16 // Field C:LMyEnumType;
39: iconst_3
40: anewarray #4 // class MyEnumType
43: dup
44: iconst_0
45: getstatic #12 // Field A:LMyEnumType;
48: aastore
49: dup
50: iconst_1
51: getstatic #14 // Field B:LMyEnumType;
54: aastore
55: dup
56: iconst_2
57: getstatic #16 // Field C:LMyEnumType;
60: aastore
61: putstatic #1 // Field $VALUES:[LMyEnumType;
64: return
}
然而的事實values()
方法必須返回一個新的數組,並不意味着編譯器必須使用的方法。編譯器可能會檢測到MyEnumType.values()[ordinal]
的使用,並且,如果看到該數組未被修改,則可能會繞過該方法並使用基本的$VALUES
陣列。 main
方法的上述反彙編顯示,javac做而不是做出這樣的優化。
我也測試過ECJ。反彙編顯示,ECJ還初始化一個隱藏數組來存儲常量(儘管Java langspec不需要這樣做),但有趣的是,它的values()
方法更傾向於創建一個空數組,然後用System.arraycopy
填充它,而不是調用.clone()
。無論哪種方式,values()
每次都會返回一個新數組。像javac的,它並沒有試圖優化順序查找:
final class MyEnumType extends java.lang.Enum<MyEnumType> {
public static final MyEnumType A;
public static final MyEnumType B;
public static final MyEnumType C;
private static final MyEnumType[] ENUM$VALUES;
static {};
Code:
0: new #1 // class MyEnumType
3: dup
4: ldc #14 // String A
6: iconst_0
7: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #19 // Field A:LMyEnumType;
13: new #1 // class MyEnumType
16: dup
17: ldc #21 // String B
19: iconst_1
20: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #22 // Field B:LMyEnumType;
26: new #1 // class MyEnumType
29: dup
30: ldc #24 // String C
32: iconst_2
33: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
36: putstatic #25 // Field C:LMyEnumType;
39: iconst_3
40: anewarray #1 // class MyEnumType
43: dup
44: iconst_0
45: getstatic #19 // Field A:LMyEnumType;
48: aastore
49: dup
50: iconst_1
51: getstatic #22 // Field B:LMyEnumType;
54: aastore
55: dup
56: iconst_2
57: getstatic #25 // Field C:LMyEnumType;
60: aastore
61: putstatic #27 // Field ENUM$VALUES:[LMyEnumType;
64: return
private MyEnumType(java.lang.String, int);
Code:
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #31 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
6: return
public static void main(java.lang.String[]);
Code:
0: getstatic #35 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #41 // Method values:()[LMyEnumType;
6: iconst_0
7: aaload
8: invokevirtual #45 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
11: return
public static MyEnumType[] values();
Code:
0: getstatic #27 // Field ENUM$VALUES:[LMyEnumType;
3: dup
4: astore_0
5: iconst_0
6: aload_0
7: arraylength
8: dup
9: istore_1
10: anewarray #1 // class MyEnumType
13: dup
14: astore_2
15: iconst_0
16: iload_1
17: invokestatic #53 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
20: aload_2
21: areturn
public static MyEnumType valueOf(java.lang.String);
Code:
0: ldc #1 // class MyEnumType
2: aload_0
3: invokestatic #59 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #1 // class MyEnumType
9: areturn
}
然而,它仍然是潛在的可能是JVM可以有一個檢測到陣列複製,然後扔掉的事實優化,並避免它。爲了測試它,我運行了以下一對基準測試程序,它們測試循環中的序號查找,每次都調用values()
,另一個使用數組的私有副本。序查詢的結果被分配到volatile
場以防止它被優化掉:
enum MyEnumType1 {
A, B, C;
public static void main(String[] args) {
long t = System.nanoTime();
for (int n = 0; n < 100_000_000; n++) {
for (int i = 0; i < 3; i++) {
dummy = values()[i];
}
}
System.out.printf("Done in %.2f seconds.\n", (System.nanoTime() - t)/1e9);
}
public static volatile Object dummy;
}
enum MyEnumType2 {
A, B, C;
public static void main(String[] args) {
long t = System.nanoTime();
for (int n = 0; n < 100_000_000; n++) {
for (int i = 0; i < 3; i++) {
dummy = values[i];
}
}
System.out.printf("Done in %.2f seconds.\n", (System.nanoTime() - t)/1e9);
}
public static volatile Object dummy;
private static final MyEnumType2[] values = values();
}
我跑這個關於Java 8u60,服務器虛擬機上。每個使用values()
方法的測試花費了大約10秒,而使用專用陣列的每個測試花費了大約2秒。使用-verbose:gc
JVM參數顯示,在使用values()
方法時存在重要的垃圾收集活動,使用私有陣列時沒有。在客戶端虛擬機上運行相同的測試,私有陣列仍然很快,但方法變得更慢,需要花費一分鐘才能完成。調用values()
也花費了更長的時間,更多的枚舉常量被定義。所有這些表明values()
方法確實每次都分配一個新數組,而避免這種方法可能會有所幫助。請注意0和java.util.EnumMap
需要使用枚舉常量數組。爲了表現,他們調用JRE專有代碼,緩存存儲在java.lang.Class
中的values()
in a shared array的結果。您可以通過調用sun.misc.SharedSecrets.getJavaLangAccess().getEnumConstantsShared(MyEnumType.class)
來訪問該共享陣列,但依賴它是不安全的,因爲這些API不是任何規範的一部分,並且可以在任何Java更新中進行更改或刪除。
結論:
values()
方法具有表現得好像它總是分配一個新的數組,如果呼叫者修改它。
噢,出於這個理由,這是爲了安全起見 - 如果它每次都返回相同的數組,那麼有人可以改變內容,然後你會看到更改後的數組,而不是原來正確的數組。 –
@Slanec我稍微更新了我的答案。 – Pshemo
「你可以簡單地用==運算符來測試它」,它只顯示你的特定Java實現每次都創建一個新的數組。這並不表示它必須這樣做。 – Raedwald