Порядок выполнения перечисления в java

У меня вопрос о перечислении.

У меня есть класс перечисления, как показано ниже

public enum FontStyle {
    NORMAL("This font has normal style."),
    BOLD("This font has bold style."),
    ITALIC("This font has italic style."),
    UNDERLINE("This font has underline style.");

    private String description;

    FontStyle(String description) {
        this.description = description;
    }
    public String getDescription() {
        return this.description;
    }
}

интересно, когда этот объект перечисления создается.

перечисление выглядит как "статический конечный" объект, так как его значение никогда не изменится. Поэтому в этой цели эффективно инициализировать только во время компиляции.

но он вызывает свой собственный конструктор в top, поэтому я сомневаюсь, что он может генерировать всякий раз, когда мы его вызываем, например, в инструкции switch.

4 ответов


да, перечисления являются статическими константами, но не являются константами времени компиляции. Как и любые другие классы, перечисление загружается при первой необходимости. Вы можете легко наблюдать за ним, если немного измените его конструктор

FontStyle(String description) {
    System.out.println("creating instace of "+this);// add this
    this.description = description;
}

и использовать простой тестовый код типа

class Main {
    public static void main(String[] Args) throws Exception {
        System.out.println("before enum");
        FontStyle style1 = FontStyle.BOLD;
        FontStyle style2 = FontStyle.ITALIC;
    }
}

если вы используете main метод вы увидите выход

before enum
creating instace of NORMAL
creating instace of BOLD
creating instace of ITALIC
creating instace of UNDERLINE

который показывает, что класс enum был загружен (и его статические поля были инициализированы), когда мы хотели использовать enum первым время.

вы также можете использовать

Class.forName("full.packag.name.of.FontStyle");

чтобы вызвать его нагрузку, если он еще не был загружен.


TLDR: значения перечисления являются константами, созданными один раз во время выполнения, во время инициализации фазы загрузки класса enum. Это эффективно, так как значения перечисления создаются только один раз.

ответ: Перечисления не являются магическими элементами, но требуется некоторое время, чтобы понять, как они работают. Поведение перечисления связано с процесс загрузки Class который можно суммировать 3 фазы:

  • загрузка: байт-код класса загружается classloader
  • связь: иерархия классов разрешена (существует подфаза разрешение)
  • инициализации: класс инициализируется вызовом статических блоков инициализатора

поясним это, используя следующий класс enum:

package mypackage;
public enum MyEnum {
    V1, V2;
    private MyEnum() {
        System.out.println("constructor "+this);
    }
    static {
        System.out.println("static init");
    }
    {
        System.out.println("block "+this);
    }
}

для того, чтобы понять как это работает для перечислений, позволяет декомпилировать код с помощью javap -c MyEnum. Это научит нас, что:

  1. перечисление реализовано как подкласс java.ленг.Перечислимые
  2. значений enum-константы (т. е. public static final значения) в классе
  3. все значения перечисления создаются в начале блока статического инициализатора, таким образом, они создаются в инициализации фаза процесса загрузки, поэтому после байт-кода загрузка и зависимости связь фазы. Поскольку они создаются в статическом блоке инициализатора, он выполняется только один раз (и не каждый раз, когда мы используем перечисление в коммутаторе).
  4. MyEnum.values() возвращает список всех значений перечисления в виде неизменяемой копии массива значений перечисления.

декомпилированный код следующий:

// 1. an enum is implemented as a special class
public final class mypackage.MyEnum extends java.lang.Enum<mypackage.MyEnum> {
  public static final mypackage.MyEnum V1; // 2. enum values are constants of the enum class
  public static final mypackage.MyEnum V2;

  static {};
    Code: // 3. all enum values are created in the static initializer block
        // create the enum value V1
       0: new           #1                  // class mypackage/MyEnum
       3: dup
       4: ldc           #14                 // String V1
       6: iconst_0
       7: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #19                 // Field V1:Lmypackage/MyEnum;

          // create the enum value V2
      13: new           #1                  // class mypackage/MyEnum
      16: dup
      17: ldc           #21                 // String V2
      19: iconst_1
      20: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic     #22                 // Field V2:Lmypackage/MyEnum;

         // create an array to store all enum values
      39: iconst_2
      40: anewarray     #1                  // class mypackage/MyEnum

      43: dup
      44: iconst_0
      45: getstatic     #19                 // Field V1:Lmypackage/MyEnum;
      48: aastore

      49: dup
      50: iconst_1
      51: getstatic     #22                 // Field V2:Lmypackage/MyEnum;
      54: aastore

      61: putstatic     #27                 // Field ENUM$VALUES:[Lmypackage/MyEnum;

      64: getstatic     #29                 // Field java/lang/System.out:Ljava/io/PrintStream;
      67: ldc           #35                 // String "static init"
      69: invokevirtual #37                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      72: return

  public static mypackage.MyEnum[] values();
    Code:       // 4. it returns an immutable copy of the field ENUM$VALUES
       0: getstatic     #27                 // Field ENUM$VALUES:[Lmypackage/MyEnum;
       3: dup
       4: astore_0
       5: iconst_0
       6: aload_0
       7: arraylength
       8: dup
       9: istore_1
      10: anewarray     #1                  // class mypackage/MyEnum
      13: dup
      14: astore_2
      15: iconst_0
      16: iload_1
      17: invokestatic  #67                 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V  (=immutable copy)
      20: aload_2
      21: areturn

  public static mypackage.MyEnum valueOf(java.lang.String);
    Code:
       0: ldc           #1                  // class mypackage/MyEnum
       2: aload_0
       3: invokestatic  #73                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #1                  // class mypackage/MyEnum
       9: areturn
}

следовательно, значения перечисления создаются при выполнении блока статического инициализатора, то есть в инициализации этап. Это можно сделать одним из следующих методов:

  • при первом получении значения перечисления (например,System.out.println(MyEnum.V1))
  • при выполнении статического метода перечисления (например,MyEnum.valueOf() или MyEnum.myStaticMethod())
  • С Class.forName("mypackage.MyEnum") (что означает загрузка, связь и инициализации фазы)
  • при вызове MyEnum.class.getEnumConstants()
перечисление значения не будут инициализированы следующей операцией (которая выполняет только загрузка фаза, и потенциально связь этап):
  • MyEnum.class.anyMethod() (кроме getEnumConstants() конечно): в основном мы получаем доступ только метаданные класса, а не выполнение
  • Class.forName("myPackage.MyEnum", false, aClassLoader): the false параметр value указывает загрузчику классов избегать инициализации этап
  • ClassLoader.getSystemClassLoader().loadClass("myPackage.MyEnum"): явно не только загрузка этап.

некоторые забавные другие факты о перечислениях:

  • Class<MyEnum>.getInstance() вызывает исключение: потому что в перечислении
  • the инициализация блоков выполнения заказов, кажется, отменяется от обычного (первый инициализатор экземпляра block V1, затем конструктор block constructor V1, тогда статический инициализатор static init): из декомпилированного кода мы увидели, что значения перечисления инициализация происходит в начале статического блока инициализатора. Для каждого значения перечисления этот статический инициализатор создает новый экземпляр, который вызывает блок инициализатора экземпляра, а затем блок конструктора. Статический инициализатор завершается выполнением пользовательского блока статического инициализатора.

экземпляры перечисления создаются только один раз при загрузке самого класса перечисления.

очень важно, чтобы они создавались только один раз, чтобы сравнение идентификаторов объектов работало (==). Даже механизм сериализации объекта (de) должен был быть скорректирован для поддержки этого.


экземпляры перечисления создаются во время связывание классов (разрешение), который является этапом, который приходит после загрузка класс, как и статические поля "нормального" класса.

связывание классов происходит отдельно от загрузки классов. Поэтому, если вы динамически загружаете класс Enum с помощью загрузчика классов, константы будут созданы только при попытке доступа к одному из экземпляров, например, при использовании метода getEnumConstants() от Class.

вот немного кода для проверки приведенного выше утверждения:

File1:TestEnum.java

public enum TestEnum {

    CONST1, CONST2, CONST3;

    TestEnum() {
        System.out.println( "Initializing a constant" );
    }
}

File2: