A Preliminary study on enumeration types

Enumeration types, whose syntax is always weird, look like this:

public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER
}
Copy the code

A single line defines an enumerated type with four values, which is always syntactically weird. While in use:

public void test() {
    System.out.println("spring name:" + Season.SPRING.name());
    System.out.println("spring ordinal:" + Season.SPRING.ordinal());
    System.out.println("autumn name:" + Season.AUTUMN.name());
    System.out.println("autumn ordinal:" + Season.AUTUMN.ordinal());
}
Copy the code

You get the output:

spring name:SPRING
spring ordinal:0
autumn name:AUTUMN
autumn ordinal:2
Copy the code

So what happens in our simple one-line definition? How are enumerated types implemented at compile time? What other characteristics does it have?

We’ll cover enumeration types in Java in several sections.

Before enumeration

If we do not use enumeration, we assign each of the four values “spring, summer, autumn and winter” a number, then the common operation is:

public class Season {
    public static final int SPRING = 0;
    public static final int SUMMER = 1;
    public static final int AUTUMN = 2;
    public static final int WINTER = 3;
}
Copy the code

The above method definition is cumbersome and error prone. For example, the compiler will not give any warning if the int number we define is repeated. At the same time, such operations became so frequent that enumerations were eventually added to Java 5.

Instead, with an enumerated type, everything becomes a simple line like this:

public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER
}
Copy the code

Furthermore, Java automatically assigns numbers starting from 0 in the order in which the enumerated values appear. The name of the enumeration value can be obtained by name(), and the number of the enumeration value can be obtained by ordinal().

Implementation principle of enumeration

So what happens when we define an enumerated type? We explore the implementation principle of enumeration.

First, we implemented the Season enumeration class without defining the name() and ordinal() methods. Starting with this, we click on the method and find ourselves in an abstract class:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
        }
Copy the code

Also, we found that the compiler would not allow us to implement the abstract class ourselves to construct a new one. However, since our Season enumeration class can call its methods, the Season enumeration class should inherit from this abstract class. To test this hypothesis, we asked the Season class to inherit from another class and found that it couldn’t because Java doesn’t allow multiple inheritance.

Java -p Season. Class Java -p Season. Class

public final class com.test.constant.Season extends
    java.lang.Enum<com.test.constant.Season> {
  public static final com.test.constant.Season SPRING;
  public static final com.test.constant.Season SUMMER;
  public static final com.test.constant.Season AUTUMN;
  public static final com.test.constant.Season WINTER;
  private static final com.test.constant.Season[] $VALUES;
  public static com.test.constant.Season[] values();
  public static com.test.constant.Season valueOf(java.lang.String);
  private com.test.constant.Season();
  static {};
}
Copy the code

We see that there are a number of points to note with enumerated classes:

  • Enumeration classes do generate a class that extends java.lang.enum when compiled
  • The enumeration class is final, so we can no longer inherit it
  • Each enumerated value we define is a member of the class, and the member is still of type Season
  • Enumeration classes have many static methods added by default, such as values() and so on

To learn more about the operations in each method, we use the Java -p -c season-.class bytecode in each method:

public final class com.test.constant.Season extends java .lang.Enum<com.test.constant.Season> { public static final com.test.constant.Season SPRING; public static final com.test.constant.Season SUMMER; public static final com.test.constant.Season AUTUMN; public static final com.test.constant.Season WINTER; private static final com.test.constant.Season[] $VALUES; public static com.test.constant.Season[] values(); Code: 0: getstatic #1 // Field $VALUES:[Lcom/test/constant/Season; 3: invokevirtual #2 // Method "[Lcom/test/constant/Season;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[Lcom/test/constant/Season;" 9: areturn public static com.test.constant.Season valueOf(java.lang.String); Code: 0: ldc #4 // class com/test/constant/Season 2: aload_0 3: invokestatic #5 // Method java/lang/Enum.valueOf: (Ljava/lang/Class;Ljava/lang/String;)Ljavalang/Enum; 6: checkcast #4 // class com/test/constant/Season 9: Areturn // omit some method bytecode}Copy the code

According to the bytecode, we restore its operation code, which is roughly as follows:

Note: the following code for the author’s own translation, if you have a good bytecode view tool must tell me ah! It’s too much trouble to turn by yourself.

public final class com.test.constant.Season extends java .lang.Enum<com.test.constant.Season> { public static final com.test.constant.Season SPRING; public static final com.test.constant.Season SUMMER; public static final com.test.constant.Season AUTUMN; public static final com.test.constant.Season WINTER; private static final com.test.constant.Season[] $VALUES; public static com.test.constant.Season[] values(){ return (Season[])$VALUES.clone(); } public static com.test.constant.Season valueOf(String s){ return (Season)Enum.valueOf(java/lang/String, s); } private com.test.constant.Season(String s, int i){ super(s,i); } static { SPRING = new Season("SPRING", 0); SUMMER = new Season("SUMMER", 1); AUTUMN = new Season("AUTUMN", 2); WINTER = new Season("WINTER", 3); $VALUES = (new Season[] { SPRING, SUMMER, AUTUMN, WINTER }); }}Copy the code

We can see here that in the class static operation, the compiler helps us generate objects for each enumerated value.

conclusion

To summarize, enumeration types we define with enum are converted to a class that inherits java.lang. enum after compilation, and each enumeration value we define is instantiated as an object of the enumerated class we define during class initialization. The compiler also added two methods to the class: values() and valueOf().

At this point, we have a thorough understanding of Java enumeration objects.

But we still have a lot of questions:

  • Is the way the compiler inserts different from the normal way
  • What other considerations are there for using enumerated types, such as related methods and operations
  • What else is special about the implementation of enumerated types

We will continue to analyze these in the following articles.

Finally, I am Yi Ge, architect, welcome to follow me, I will occasionally come and share software architecture and programming related dry knowledge.