Подтвердить что ты не робот

Значения Enum #() выделяют память для каждого вызова?

Мне нужно преобразовать порядковое значение int в значение enum в Java. Что просто:

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

Метод values() неявный, и я не могу найти исходный код для него, поэтому вопрос.

Выделяет ли MyEnumType.values() новый массив или нет? И если это так, я должен кэшировать массив при первом вызове? Предположим, что преобразование будет вызываться довольно часто.

4b9b3361

Ответ 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() должен вести себя так, как будто он всегда выделяет новый массив, если вызывающие его изменяют.
  • Компиляторы или виртуальные машины могут в некоторых случаях оптимизировать выделение, но, по-видимому, они этого не делают.
  • В критическом для производительности кодексе стоит взять собственную копию массива.