Ответ 1
Да, 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];
}
-
вы также можете сохранить результат values()
в немодифицируемой коллекции, например List
, чтобы гарантировать, что ее содержимое не будет изменено (теперь такой список может быть общедоступным).
public static final List<X> VALUES = Collections.unmodifiableList(Arrays.asList(values()));
Ответ 2
Теоретически метод 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]);
}
}
Затем скомпилируйте его и разоберите его с помощью инструмента javap, включенного в JDK:
javac MyEnumType.java && javap -c -p MyEnumType
Видимыми в выходе являются все неявные члены перечисления, сгенерированные компилятором, включая (1) поле static final
для каждой константы перечисления, (2) скрытый массив $VALUES
, содержащий все константы, (3) статический блок инициализатора, который создает каждую константу и присваивает каждому ее имя и массив, и (4) метод values()
, который работает, вызывая .clone()
в массиве $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, на VM сервера. Каждый тест с использованием метода values()
занял около 10 секунд, в то время как каждый тест с использованием частного массива занял около 2 секунд. Использование аргумента -verbose:gc
JVM показало, что при использовании метода values()
была значительная деятельность по сбору мусора, а при использовании частного массива - нет. Выполняя те же тесты на виртуальной машине клиента, частный массив все еще был быстрым, но метод values()
стал еще медленнее, заработав минуту. Вызов values()
также занимал больше времени, чем больше констант перечисления. Все это указывает на то, что метод values()
действительно выделяет новый массив каждый раз, и это может быть выгодным.
Обратите внимание, что для java.util.EnumSet
и java.util.EnumMap
необходимо использовать массив констант перечисления. Для производительности они вызывают собственный код JRE, который кэширует результат values()
в общем массиве, хранящийся в java.lang.Class
. Вы можете получить доступ к этому разделяемому массиву самостоятельно, вызвав sun.misc.SharedSecrets.getJavaLangAccess().getEnumConstantsShared(MyEnumType.class)
, но небезопасно полагаться на него, поскольку такие API не являются частью каких-либо спецификаций и могут быть изменены или удалены в любом обновлении Java.
Вывод:
- Метод enum
values()
должен вести себя так, как будто он всегда выделяет новый массив, если вызывающие его изменяют.
- Компиляторы или виртуальные машины могут в некоторых случаях оптимизировать выделение, но, по-видимому, они этого не делают.
- В критическом для производительности кодексе стоит взять собственную копию массива.