2015-09-26 56 views
18

我需要將一個有序的int值轉換爲Java中的枚舉值。這很簡單:Enum#values()是否在每次調用時分配內存?

MyEnumType value = MyEnumType.values()[ordinal]; 

values()方法是隱含的,我無法找到它的源代碼,因此問題。

MyEnumType.values()是否分配新的數組?如果是這樣,我應該在第一次調用時緩存數組嗎?假設轉換經常被調用。

回答

21

是的,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像Listvalues()也將結果保存,以確保其內容不會被改變(現在這樣的列表可以是公開的)。

    public static final List<X> VALUES = Collections.unmodifiableList(Arrays.asList(values())); 
    
+3

噢,出於這個理由,這是爲了安全起見 - 如果它每次都返回相同的數組,那麼有人可以改變內容,然後你會看到更改後的數組,而不是原來正確的數組。 –

+1

@Slanec我稍微更新了我的答案。 – Pshemo

+0

「你可以簡單地用==運算符來測試它」,它只顯示你的特定Java實現每次都創建一個新的數組。這並不表示它必須這樣做。 – Raedwald

7

從理論上講,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()方法具有表現得好像它總是分配一個新的數組,如果呼叫者修改它。
  • 在某些情況下,編譯器或虛擬機可能會優化分配,但顯然它們不會。
  • 在對性能要求很高的代碼中,非常值得自己複製數組。
相關問題