2015-09-26 8 views
18

Devo convertire un valore ordinale int in un valore enum in Java. Quale è semplice:Il valore Enum # values ​​() assegna memoria ad ogni chiamata?

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

Il metodo values() è implicito, e non riesco a trovare il codice sorgente per questo, da qui la domanda.

Il MyEnumType.values() alloca un nuovo array o no? E se lo fa, dovrei mettere in cache l'array al primo richiamo? Supponiamo che la conversione venga chiamata abbastanza spesso.

risposta

21

Sì, MyEnumType.values() crea ogni volta un nuovo array che viene riempito con elementi enum. Puoi testarlo semplicemente con l'operatore ==.

MyEnumType[] arr1 = MyEnumType.values(); 
MyEnumType[] arr2 = MyEnumType.values(); 
System.out.println(arr1==arr2); //false 

Java non dispone di un meccanismo che consente di creare un array non modificabile. Quindi se values() restituisse sempre la stessa istanza di array, rischieremmo che qualcuno possa cambiare il suo contenuto per tutti.
Quindi, finché non verrà introdotto il meccanismo di matrice non modificabile su Java, per evitare il problema della mutazione delle matrici il metodo values() deve creare e restituire la copia dell'array originale.

Se si desidera evitare di ricreare questo array, è sufficiente memorizzarlo e riutilizzare il risultato di values() in seguito. Ci sono alcuni modi per farlo, come.

  • è possibile creare array di privati ​​e consentire l'accesso al suo contenuto solo tramite metodo getter come

    private static final MyEnumType[] VALUES = values();// to avoid recreating array 
    
    MyEnumType getByOrdinal(int){ 
        return VALUES[int]; 
    } 
    
  • è anche possibile memorizzare il risultato di values() nella raccolta immodificabile come List per garantire che il suo contenuto non sarà essere cambiato (ora tale elenco può essere pubblico).

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

Oh, e per il ragionamento su questo, è per sicurezza - se restituisse lo stesso array ogni volta, qualcuno potrebbe cambiare il contenuto e si vedrebbe quindi l'array modificato, non quello originale e corretto. –

+1

@Slanec Ho aggiornato la mia risposta un po '. – Pshemo

+0

"Puoi testarlo semplicemente con operatore ==" che mostra solo che la tua particolare implementazione Java crea ogni volta una nuova matrice. Non indica che * deve * farlo. – Raedwald

7

In teoria, il metodo values() deve restituire un nuovo array ogni volta, dal momento che Java non ha array immutabili. Se restituiva sempre lo stesso array, non poteva impedire che i chiamanti si confondessero a vicenda modificando la matrice.

non riesco a trovare il codice sorgente per questo

Il metodo values() non ha il codice sorgente ordinaria, essendo generato dal compilatore. Per javac, il codice che genera il metodo values() è com.sun.tools.javac.comp.Lower.visitEnumDef. Per ECJ (compilatore di Eclipse), il codice è org.eclipse.jdt.internal.compiler.codegen.CodeStream.generateSyntheticBodyForEnumValues.

Un modo più semplice per trovare l'implementazione del metodo values() consiste nel disassemblare un enum compilato.Prima creare qualche stupido enum:

enum MyEnumType { 
    A, B, C; 

    public static void main(String[] args) { 
     System.out.println(values()[0]); 
    } 
} 

Poi compilare, e smontare utilizzando lo strumento javap incluso nel JDK:

javac MyEnumType.java && javap -c -p MyEnumType 

visibile nell'output sono tutti i generati dal compilatore membri implicite del enum, incluso (1) un campo static final per ogni costante enum, (2) un array nascosto $VALUES contenente tutte le costanti, (3) un blocco di inizializzatore statico che crea un'istanza di ciascuna costante e assegna ciascuna al campo denominato e all'array, e (4) il metodo values() che funziona chiamando .clone() o n il $VALUES matrice e restituendo il risultato:

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 
} 

Tuttavia, il fatto che il metodo values() deve restituire un nuovo array, non significa il compilatore deve utilizzare il metodo. Potenzialmente un compilatore potrebbe rilevare l'uso di MyEnumType.values()[ordinal] e, visto che l'array non viene modificato, potrebbe ignorare il metodo e utilizzare l'array $VALUES sottostante. Lo smontaggio precedente del metodo main mostra che javac fa non effettuare tale ottimizzazione.

Ho anche testato ECJ. Lo smontaggio mostra che ECJ inizializza anche una matrice nascosta per memorizzare le costanti (sebbene il Java langspec non lo richieda), ma è interessante notare che il suo metodo values() preferisce creare una matrice vuota, quindi riempirlo con System.arraycopy anziché chiamare .clone(). In entrambi i casi, values() restituisce una nuova matrice ogni volta. Come javac, non tenta di ottimizzare la ricerca ordinale:

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 
} 

Tuttavia, è ancora potenzialmente possibile che la JVM potrebbe avere un'ottimizzazione che rileva il fatto che la matrice viene copiato e poi gettato via, e lo evita. Per verificarlo, ho eseguito la seguente coppia di programmi di benchmark che testano la ricerca ordinale in un ciclo, uno che chiama ogni volta values() e l'altro che utilizza una copia privata dell'array. Il risultato della ricerca ordinale viene assegnato a un campo volatile per impedire che venga ottimizzata via:

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(); 
} 

ho eseguito questo su Java 8u60, sul server VM. Ogni test che utilizzava il metodo values() richiedeva circa 10 secondi, mentre ogni test che utilizzava l'array privato richiedeva circa 2 secondi. L'utilizzo dell'argomento JVM -verbose:gc ha mostrato che c'era un'attività di garbage collection significativa quando veniva utilizzato il metodo values() e none quando si utilizzava l'array privato. L'esecuzione degli stessi test sulla VM client, l'array privato era ancora veloce, ma il metodo values() è diventato ancora più lento, richiedendo oltre un minuto per terminare. Anche la chiamata a values() ha richiesto più tempo per definire le costanti di enumerazione. Tutto ciò indica che il metodo values() assegna effettivamente un nuovo array ogni volta e che evitarlo può essere vantaggioso.

Nota che sia java.util.EnumSet sia java.util.EnumMap devono utilizzare l'array di costanti enum. Per le prestazioni chiamano il codice proprietario JRE che memorizza nella cache il risultato di values()in a shared array memorizzato in java.lang.Class. Puoi accedere direttamente a quell'array condiviso chiamando sun.misc.SharedSecrets.getJavaLangAccess().getEnumConstantsShared(MyEnumType.class), ma non è sicuro dipenderlo poiché tali API non fanno parte di alcuna specifica e possono essere modificate o rimosse in qualsiasi aggiornamento Java.

Conclusione:

  • L'enumerazione values() metodo deve comportarsi come se fosse sempre alloca un nuovo array, in caso chiamanti modificarlo.
  • I compilatori o le macchine virtuali potrebbero potenzialmente ottimizzare tale allocazione in alcuni casi, ma a quanto pare non lo fanno.
  • Nel codice critico delle prestazioni, vale la pena prendere la propria copia dell'array.
Problemi correlati