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.
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. –
@Slanec Ho aggiornato la mia risposta un po '. – Pshemo
"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