In depth analysis of Java enumeration classes at bytecode level

Posted by MBDesktop on Thu, 27 Jan 2022 16:01:47 +0100

Use of enumeration classes

Define a simple enumeration class, which contains several enumeration constants. Examples are as follows:

public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,THURSDAY, FRIDAY, SATURDAY 
}

Java's switch statement parameters support the use of enumeration classes

// Day is a variable of type day
switch (day) {
    case MONDAY:
        System.out.println("It's hard to have a group meeting");
        break;
    case THURSDAY:
        System.out.println("KFC crazy Thursday");
        break;
    case SATURDAY: case SUNDAY:
        System.out.println("No, sorry weekend");
        break;
    default:
        System.out.println("Touch fish");
        break;
}

In fact, static final can also be used, but the advantages of enumerating classes are:

  • If static final is used, the parameters of the passed in variables need to be checked, but the enumeration class does not, because it must be in the scope of enumeration or null
  • static final does not support attribute extension. Each variable name corresponds to a value, and each enumerated value can have its own multiple attributes (fields)

Enumeration classes can add properties, corresponding constructors, and methods, but constructors in enumeration classes must also be private by default. Examples are as follows:

public enum Day {
    MONDAY(1, "Monday"),
    TUESDAY(2, "Tuesday"),
    // ......
    SUNDAY(7, "Sunday");

    private int index;
    private String name;
    
    Day(int index, String name) {
        this.index = index;
        this.name = name;
    }
    
    // getter method for index and name
    // ......
}

Generally speaking, setter methods will not be added to enumeration classes, because enumeration is generally only used as constants, and setter will destroy its constant characteristics

Each enumeration constant in the enumeration class can implement the abstract method defined by the enumeration main class or have its own internal method, as follows:

public enum Day {
    MONDAY(1, "Monday"){
        @Override
        public Day getNext() {
            return TUESDAY;
        }
    },
    TUESDAY(2, "Tuesday"){
        @Override
        public Day getNext() {
            return WEDNESDAY;
        }
    },
    // ......
    SUNDAY(7, "Sunday"){
        @Override
        public Day getNext() {
            return MONDAY;
        }
    };

    private int index;
    private String name;
    
    Day(int index, String name) {
        this.index = index;
        this.name = name;
    }
    
    // Define abstract methods in the main class
    public abstract Day getNext();

    // getter method for index and name
    // ......
}

Although any method can also be customized in each enumeration constant, it cannot be accessed if it is not declared in the main class. This is not shown in the following table for the time being. The reason will be explained later

All enumeration classes inherit Enum class by default. You can directly use the practical methods provided by Enum. Because Java only supports single inheritance, enumeration classes can no longer inherit other parent classes and can only implement interfaces. Some methods of Enum class used are posted at the end of the article_

The enumeration constant of the enumeration class is globally unique, there is no concurrency security problem, and it will not be reflected or serialized to maliciously create new enumeration constant objects. It is very suitable for implementing singleton mode. Here you can join another blog post: Various implementations of singleton mode (Java)

Finally, bloggers found that a book and many blogs said that when comparing the values of two enumeration types, you never need to call the equals method, but just use = =. But I looked at the equals source code given in the Enum class (posted below). In fact, I used = =, and I had no problem testing it manually. But I don't know why. Everyone writes that on their blogs. Is it really like everyone else-_-||

public final boolean equals(Object other) {
    return this==other;
}
Author: Wine lie Source: https://www.cnblogs.com/frankiedyz/p/15851123.html
Copyright: the copyright of this article belongs to the author and the blog park
Reprint: reprint is welcome, but this statement must be retained without the consent of the author; The original connection must be given in the article; Otherwise, legal liability will be investigated

Research on the underlying principle of enumeration

To study a problem, it is best to look at the essence from the phenomenon. First know what phenomena exist, and then see what their essential reasons are. The difference between enumeration class and ordinary class is phenomenon:

  • Enumeration class cannot be created by external objects
  • Enum class inherits enum class by default, and cannot inherit other classes, and enum class cannot be inherited
  • Although there are no values() and valueOf(String) methods in Enum class, enumeration class can also be called
  • The enumeration main class can define abstract methods, and each enumeration constant can be implemented separately, but they can also customize other methods. However, only the methods defined in the main class can be called through enumeration constants, otherwise they cannot be used
  • The fields defined in each enumeration main class are different in each enumeration constant
  • All enumeration constants of enumeration class are created from the beginning, which are globally unique, immutable and thread safe

First, write an ordinary enumeration class as an example. The code is as follows:

public enum Color {
    red("red", 0) {
        @Override
        public void print() {
            System.out.println(getName() + ":" + getIndex());
        }
    },
    green("green", 1) {
        @Override
        public void print() {
            System.out.println(getName() + " " + getIndex());
        }
    };

    private String name;
    private int index;

    Color() {
    }

    Color(String name, int index) {
        this.name = name;
        this.index = index;
    }

    // Enumerate the abstract methods defined in the main class
    public abstract void print();

    // Getter method of name and index
}

First, color Class to decompile (javap without parameter - v):

Compiled from "Color.java"
public abstract class com.duyuzhou.enumTest.Color extends java.lang.Enum<com.duyuzhou.enumTest.Color> {
  public static final com.duyuzhou.enumTest.Color red;
  public static final com.duyuzhou.enumTest.Color green;
  public static com.duyuzhou.enumTest.Color[] values();
  public static com.duyuzhou.enumTest.Color valueOf(java.lang.String);
  public java.lang.String getName();
  public int getIndex();
  public abstract void print();
  public static void main(java.lang.String[]);
  com.duyuzhou.enumTest.Color(java.lang.String, int, java.lang.String, int, com.duyuzhou.enumTest.Color$1);
  static {};
}

After careful analysis, the following conclusions can be drawn:

  • Enumeration class inherits Enum class and various methods of Enum class. The compiler adds the final keyword to the enumeration class, so that the enumeration class cannot be inherited by other classes
  • The enumeration class is added with two additional static methods by the compiler: values() and valueOf(String)
  • Enumeration classes are compiled into abstract classes, so enumeration classes can define abstract methods
  • The constructor of enumeration class has been changed to private, so the outside world cannot use enumeration class to create objects
  • All enumeration constants will be compiled into the static final attribute of the main enumeration class, so all enumeration constants will be created in the initialization phase of class loading, and will only be created once. Final can also ensure that once the enumeration constant is created, it will be visible to all threads and there will be no thread safety problem

If you decompile with the - v parameter, you can see that Color has two static internal classes, Color and Color.. Decompile one of them (without - v):

Compiled from "Color.java"
final class com.duyuzhou.enumTest.Color$1 extends com.duyuzhou.enumTest.Color {
  com.duyuzhou.enumTest.Color$1(java.lang.String, int, java.lang.String, int);
  public void print();
}

In fact, each static inner class corresponds to an enumeration constant. These static inner classes inherit the enumeration main class, so the abstract method defined in the main class can be implemented in the enumeration constant. Moreover, in the main enumeration class, each enumeration constant becomes a field of the type of the main enumeration class, so it is impossible for the outside world to call a private custom method in the enumeration constant but not defined in the main enumeration class

In addition, since each enumeration constant is an object of a different enumeration subclass, they inherit the fields defined by the parent class respectively, and observing the decompilation results of the enumeration constant, it will be found that the compiler has added a constructor for each enumeration subclass, so the fields defined in the enumeration main class are assigned separately in each enumeration constant

So far, all the above "phenomena" have been solved by decompiling and viewing bytecode. In essence, the compiler has helped us do a good job behind the scenes, so we have the actual rules that can't be seen in these codes. However, there is still a small problem left - in Color Class, why does the compiler add two additional method parameters to the constructor of Color: String and int?

Take a look at color's constructor, com duyuzhou. enumTest. Bytecode decompiled by color (java.lang.string, int, java.lang.string, int, com. Duyuzhou. Enumtest. Color $1):

0 aload_0
1 aload_1
2 iload_2
3 aload_3
4 invokespecial #2 <enumtest/Color.<init> : (Ljava/lang/String;ILjava/lang/String;)V>
7 return

Color. Is called here< Init > method. The bytecode of this method needs to be viewed with the help of Jclasslib tool, as follows:

0 aload_0
1 aload_1
2 iload_2
3 invokespecial #9 <java/lang/Enum.<init> : (Ljava/lang/String;I)V>
6 return

It should be understood from here that in fact, two additional method parameters will be passed to the constructor of the parent class Enum. Let's see how Enum receives a String and an int constructor:

protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
}

If readers want to get to the bottom of the matter, they can study the values passed to name and ordinal. Here's the answer: because the enumeration constant created by calling the constructor is modified by static final, the time of calling occurs in the initialization stage of class loading. At this time, the compiler will collect all static assignment statements and static blocks in order, generate a < clinit > method, and then execute this method. Therefore, the name and ordinal parameters can be found by analyzing the bytecode in this method. Name is actually the name of the enumeration constant, and ordinal is the declared position of the enumeration constant in the enumeration class, counting from 0. These two parameters are recorded to facilitate the calling of toString, ordinal, compareTo and other methods in Enum

You can see here, you must understand that enumeration class, a new feature added to JDK5, is just a "syntax sugar". In order to maintain backward compatibility, the java compiler has done a lot of behind the scenes work. According to this idea, we can also explore how other Java syntax sugars are implemented, such as forEach method, automatic boxing / unpacking, why generics erase types, etc

Finally, summarize the methods provided by the more practical Enum class and the two methods automatically added by the compiler for each enumeration class (values() and valueOf(String))

Methods provided by Enum class:

  • String name(): equivalent to toString()
  • int ordinal(): returns the declared position of the enumeration constant in the enumeration class, counting from 0
  • String toString(): returns the enumeration constant name
  • int compareTo(E other): compare the position of two enumeration constant declarations. In fact, it depends on comparing the size of ordinal
  • static Enum valueOf(Class clz, String name): returns a specific enumeration constant according to the enumeration class and enumeration constant name

Methods automatically added by the compiler for each enumeration class:

  • Enum[] values(): returns an array of enumeration constants. In fact, when the compiler creates each enumeration constant in < clinit >, it will also create a field $VALUES, in which the array is saved
  • Enum valueOf(String name): returns the specific enumeration constant in the enumeration class according to the enumeration constant name. The internal call is enum's valueOf method

Topics: Java jvm