9

I got a question about Enum.

I have an enum class looks like below

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

I wonder when this Enum object is created.

Enum looks like 'static final' Object since its value will never changed. So in that purpose, it is efficient to initialize in compile time only.

But it calls its own constructor in top, so I doubt that it could generate whenever we call it, for example, in switch statement.

Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
Juneyoung Oh
  • 7,318
  • 16
  • 73
  • 121

4 Answers4

11

TLDR: enum values are constants created once at runtime, during the initialization phase of the enum class loading. This is efficient as the enum values are only created once.

Long answer: Enums are not magical elements, but it takes some time to understand how they work. The enum behavior is related to the class loading process which can be summarized by 3 phases:

  • loading: the class bytecode is loaded by the classloader
  • linking: the class hierarchy is resolved (there is a subphase called resolution)
  • initializing: the class is initialized by calling the static initializer blocks

Let's explain this using the following enum class:

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

In order to understand how it works for enums, lets decompile the code using javap -c MyEnum. This will learn us that:

  1. an enum is implemented as a subclass of java.lang.Enum
  2. enum values are constants (i.e. public static final values) in the class
  3. all the enum values are created in at the beginning of static initializer block, thus they are created in the initialize phase of the loading process, so after the bytecode loading and dependencies linking phases. As they are created in the static initializer block, it is executed only once (and not every time we use the enum in a switch).
  4. MyEnum.values() returns the list of all enum values as an immutable copy of the enum values array.

The decompiled code is the following:

// 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
}

Consequently, the enum values are created when the static initializer block is executed, that is in the initialization phase. This can be done with one of the following methods:

  • the first time an enum value is obtained (e.g. System.out.println(MyEnum.V1))
  • when executing a static method of the enum (e.g. MyEnum.valueOf() or MyEnum.myStaticMethod())
  • with Class.forName("mypackage.MyEnum") (which does the loading, linking and initializing phases)
  • when calling MyEnum.class.getEnumConstants()

However the enum values will NOT be initialized with the following operation (which do only the loading phase, and potentially the linking phase):

  • MyEnum.class.anyMethod() (except getEnumConstants() of course): basically we access only the class metadatas, not the implementation
  • Class.forName("myPackage.MyEnum", false, aClassLoader): the false value parameter tells the classloader to avoid the initialization phase
  • ClassLoader.getSystemClassLoader().loadClass("myPackage.MyEnum"): explicitely does only the loading phase.

Some fun other facts about enums:

  • Class<MyEnum>.getInstance() throws an Exception: because there is no public constructor in the enum
  • the initialization blocks execution orders seems to be reversed from the usual one (first instance initializer block V1, then constructor block constructor V1, then static initializer static init): from the decompiled code, we saw that enum values initialization takes place on the beginning of the static initializer block. For each enum value, this static initializer create a new instance, which calls the instance initializer block, then the constructor block. The static initializer ends by executing the custom static initializer block.
Julien Kronegg
  • 4,968
  • 1
  • 47
  • 60
9

Yes, enums are static constants but are not compile time constants. Just like any other classes enum is loaded when first time needed. You can observe it easily if you change its constructor a little

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

and use simple test code like

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

If you will run main method you will see output

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

which shows that enum class was loaded (and its static fields have been initialized) right when we wanted to use enum first time.

You can also use

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

to cause its load if it wasn't loaded yet.

Pshemo
  • 122,468
  • 25
  • 185
  • 269
0

The enum instances are created only once, when the Enum class itself is loaded.

It is very important that they are created only once, so that object identity comparison works (==). Even the object (de)serialization mechanism had to be adjusted to support this.

Thilo
  • 257,207
  • 101
  • 511
  • 656
0

Enum instances are created during class linking (resolution), which is a stage that comes after class loading, just like static fields of a "normal" class.

Class linking happens separately from class loading. So if you dynamically load the Enum class using a class loader, the constants will be instantiated only when you actually try to access one of the instances, for example, when using the method getEnumConstants() from Class.

Here is a bit of code to test the above assertion:

File1: TestEnum.java

public enum TestEnum {

    CONST1, CONST2, CONST3;

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

File2: Test.java

class Test
{
    public static void main( String[] args ) {

        ClassLoader cl = ClassLoader.getSystemClassLoader();

        try {
            Class<?> cls = cl.loadClass( "TestEnum" );
            System.out.println( "I have just loaded TestEnum" );
            Thread.sleep(3000);
            System.out.println( "About to access constants" );
            cls.getEnumConstants();
        } catch ( Exception e ) {
            e.printStackTrace();
            System.exit(1);
        }
    }
}

The output:

I have just loaded TestEnum
... three seconds pause ...
About to access constants
Initializing a constant
Initializing a constant
Initializing a constant

The distinction is important if, for any reason, you are not using the enum plainly (just by referring to one of its constants) but instead rely on dynamically loading it.

Notes:

  • Using Class.forName() will both load and link the class, so the constants will be immediately instantiated.
  • It is enough to access one constant for the entire class to be linked, therefore all the constants will be instantiated at that time.
RealSkeptic
  • 33,993
  • 7
  • 53
  • 79
  • 3
    What is your source for this assertion? – user207421 Feb 03 '15 at 10:35
  • Over here (not authorative, of course) it says that they are initialized at class load time: http://stackoverflow.com/questions/18471653/how-java-ensures-only-one-instance-of-an-enum-per-jvm and http://stackoverflow.com/a/9408412/14955 and http://stackoverflow.com/a/18425768/14955 – Thilo Feb 03 '15 at 10:43
  • @EJP et al: I learned this while answering [this question](http://stackoverflow.com/q/28054222/4125191). I tested by creating a simple enum with a print in its constructor, then loading it using a class loader. – RealSkeptic Feb 03 '15 at 10:54
  • 1
    @Thilo et al: I have added a short test to my answer that shows that enum constants are initiated at first access. – RealSkeptic Feb 03 '15 at 11:22
  • @RealSkeptic. That does not really "load" the class, though. It still needs to be "resolved". Try `Class.forName` or `cl.loadClass(name, true);` – Thilo Feb 03 '15 at 11:27
  • @Thilo: fair enough, but that's still not at class *loading* but at class *resolution*, which are two separate stages. – RealSkeptic Feb 03 '15 at 11:32
  • I have rephrased the answer to reflect the above information. – RealSkeptic Feb 03 '15 at 12:54