Enumeration of the path of Java engineers to God

Original author: Hollis

Enumeration usage

1 background

Before the introduction of enumeration types in the java language, the common mode of representing enumeration types was to declare a set of constants with int. Previously, we usually used the public final static method to define the following codes: 1 for spring, 2 for summer, 3 for autumn and 4 for winter.

public class Season {
    public static final int SPRING = 1;
    public static final int SUMMER = 2;
    public static final int AUTUMN = 3;
    public static final int WINTER = 4;
}

This method is called int enumeration mode. But what's wrong with this model? We've been using it for so long. It should be OK. Usually the code we write will consider its security, ease of use and readability. First, let's consider its type security. Of course, this pattern is not type safe. For example, we design a function that requires a value of spring, summer, autumn and winter to be passed in. However, with int type, we cannot guarantee that the value passed in is legal. The code is as follows:

private String getChineseSeason(int season){
        StringBuffer result = new StringBuffer();
        switch(season){
            case Season.SPRING :
                result.append("spring");
                break;
            case Season.SUMMER :
                result.append("summer");
                break;
            case Season.AUTUMN :
                result.append("autumn");
                break;
            case Season.WINTER :
                result.append("winter");
                break;
            default :
                result.append("There are no seasons on earth");
                break;
        }
        return result.toString();
    }

    public void doSomething(){
        System.out.println(this.getChineseSeason(Season.SPRING));//This is a normal scenario

        System.out.println(this.getChineseSeason(5));//This is an abnormal scenario, which leads to the problem of type insecurity
    }

The program getChineseSeason(Season.SPRING) is our expected use method. But getChineseSeason(5) is obviously not, and the compilation will pass. We don't know what will happen at runtime. This obviously does not conform to the type safety of Java programs.

Next, let's consider the readability of this pattern. In most cases of using enumeration, I need to easily get the String expression of enumeration type. If the int enumeration constant is printed out, what we see is a set of numbers, which is not very useful. We might think of using String constants instead of int constants. Although it provides printable strings for these constants, it can cause performance problems because it depends on String comparison operation, so this mode is also undesirable. The disadvantages of enumeration and String mode are exposed from the two aspects of program readability. Fortunately, from Java 1 Since the release of 5, an alternative solution has been proposed, which can avoid the disadvantages of int and String enumeration mode and provide many additional benefits. That is enum type. The following chapters will introduce the definition, characteristics, application scenarios, advantages and disadvantages of enumeration types.

2 Definition

enum type refers to a legal type composed of a fixed set of constants. In Java, an enumeration type is defined by the keyword enum. The following is the definition of Java enumeration type.

public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER;
}

3 features
The Java statement defining enumeration types is very simple. It has the following characteristics:

  1. Use keyword enum
  2. Type name, such as Season here
  3. A string of allowable values, such as the four seasons of spring, summer, autumn and winter defined above
  4. Enumeration can be defined separately in a file or embedded in other Java classes. In addition to such basic requirements, users have some other options
  5. Enumeration can implement one or more interfaces
  6. You can define new variables
  7. New methods can be defined
  8. You can define classes that differ according to specific enumeration values

4 application scenarios

Taking the type safety mentioned in the background as an example, rewrite that code with enumerated types. The code is as follows:

public enum Season {
    SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);

    private int code;
    private Season(int code){
        this.code = code;
    }

    public int getCode(){
        return code;
    }
}
public class UseSeason {
    /**
     * Convert English seasons into Chinese seasons
     * @param season
     * @return
     */
    public String getChineseSeason(Season season){
        StringBuffer result = new StringBuffer();
        switch(season){
            case SPRING :
                result.append("[English: spring, enumeration constant:" + season.name() + ",data:" + season.getCode() + "]");
                break;
            case AUTUMN :
                result.append("[English: autumn, enumeration constant:" + season.name() + ",data:" + season.getCode() + "]");
                break;
            case SUMMER : 
                result.append("[English: summer, enumeration constant:" + season.name() + ",data:" + season.getCode() + "]");
                break;
            case WINTER :
                result.append("[English: winter, enumeration constant:" + season.name() + ",data:" + season.getCode() + "]");
                break;
            default :
                result.append("There are no seasons on earth " + season.name());
                break;
        }
        return result.toString();
    }

    public void doSomething(){
        for(Season s : Season.values()){
            System.out.println(getChineseSeason(s));//This is a normal scenario
        }
        //System.out.println(getChineseSeason(5));
        //The compilation fails here, which ensures type safety
    }

    public static void main(String[] arg){
        UseSeason useSeason = new UseSeason();
        useSeason.doSomething();
    }
}

[Chinese: SPRING, enumeration constant: SPRING, data: 1] [Chinese: SUMMER, enumeration constant: SUMMER, data: 2] [Chinese: AUTUMN, enumeration constant: AUTUMN, data: 3] [Chinese: WINTER, enumeration constant: WINTER, data: 4]

Here's a question. Why should I add a domain to an enumeration type? The purpose is to associate data with its constants. For example, 1 represents spring and 2 represents summer.

5 Summary

So when should I use enumeration? Whenever a set of fixed constants is needed, such as the number of days in a week, the four seasons of the year, etc. Or a collection of all the values we know before we compile. The enumeration of Java 1.5 can meet the requirements of most programmers. Its simplicity and ease of use are very prominent.

6 usage

Usage 1: constant

public enum Color {  
  RED, GREEN, BLANK, YELLOW  
}  

Usage 2: switch

enum Signal {  
    GREEN, YELLOW, RED  
}  
public class TrafficLight {  
    Signal color = Signal.RED;  
    public void change() {  
        switch (color) {  
        case RED:  
            color = Signal.GREEN;  
            break;  
        case YELLOW:  
            color = Signal.RED;  
            break;  
        case GREEN:  
            color = Signal.YELLOW;  
            break;  
        }  
    }  
}  

Three: add new method to enumeration

public enum Color {  
    RED("gules", 1), GREEN("green", 2), BLANK("white", 3), YELLO("yellow", 4);  
    // Member variable  
    private String name;  
    private int index;  
    // Construction method  
    private Color(String name, int index) {  
        this.name = name;  
        this.index = index;  
    }  
    // Common method  
    public static String getName(int index) {  
        for (Color c : Color.values()) {  
            if (c.getIndex() == index) {  
                return c.name;  
            }  
        }  
        return null;  
    }  
    // get set method  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
    public int getIndex() {  
        return index;  
    }  
    public void setIndex(int index) {  
        this.index = index;  
    }  
}  

Usage 4: override enumeration method

public enum Color {  
    RED("gules", 1), GREEN("green", 2), BLANK("white", 3), YELLO("yellow", 4);  
    // Member variable  
    private String name;  
    private int index;  
    // Construction method  
    private Color(String name, int index) {  
        this.name = name;  
        this.index = index;  
    }  
    //Coverage method  
    @Override  
    public String toString() {  
        return this.index+"_"+this.name;  
    }  
}  

Usage 5: implement interface

public interface Behaviour {  
    void print();  
    String getInfo();  
}  
public enum Color implements Behaviour{  
    RED("gules", 1), GREEN("green", 2), BLANK("white", 3), YELLO("yellow", 4);  
    // Member variable  
    private String name;  
    private int index;  
    // Construction method  
    private Color(String name, int index) {  
        this.name = name;  
        this.index = index;  
    }  
//Interface method  
    @Override  
    public String getInfo() {  
        return this.name;  
    }  
    //Interface method  
    @Override  
    public void print() {  
        System.out.println(this.index+":"+this.name);  
    }  
}  

Usage 6: use interface to organize enumeration

public interface Food {  
    enum Coffee implements Food{  
        BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO  
    }  
    enum Dessert implements Food{  
        FRUIT, CAKE, GELATO  
    }  
}

Implementation of enumeration

Java SE5 provides a new type - enumeration type of Java. The keyword enum can create a limited set of named values as a new type, and these named values can be used as conventional program components, which is a very useful function.

If you want to see the source code, you must first have a class. What kind of enumeration type is it? Is it enum? Obviously, the answer is No. enum is just a keyword like class. It is not a class. So what class maintains enumeration? Let's simply write an enumeration:

public enum t {
    SPRING,SUMMER;
}

Then we use decompilation to see how this code is implemented. The content of the decompilated code is as follows:

public final class T extends Enum
{
    private T(String s, int i)
    {
        super(s, i);
    }
    public static T[] values()
    {
        T at[];
        int i;
        T at1[];
        System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
        return at1;
    }

    public static T valueOf(String s)
    {
        return (T)Enum.valueOf(demo/T, s);
    }

    public static final T SPRING;
    public static final T SUMMER;
    private static final T ENUM$VALUES[];
    static
    {
        SPRING = new T("SPRING", 0);
        SUMMER = new T("SUMMER", 1);
        ENUM$VALUES = (new T[] {
            SPRING, SUMMER
        });
    }
}

By decompilating the code, we can see that the public final class T extends Enum indicates that this class inherits the Enum class. At the same time, the final keyword tells us that this class cannot be inherited.

When we use enmu to define an enumeration type, the compiler will automatically help us create a class of final type to inherit the Enum class, so the enumeration type cannot be inherited.

Enumeration and singleton

There are many articles on singleton mode in my blog. As the most commonly used design pattern among the 23 design patterns, the singleton pattern is not as simple as expected. Because many problems should be considered when designing a singleton, such as thread safety, the destruction of serialization to the singleton, and so on.

As we know, there are generally seven ways to write singleton mode, so which is the best of the seven ways? Why? In this article, let's take out the silk and peel the cocoon.

Which is the best way to write a single example

In StackOverflow, what is an effective way to implement a single pattern in Java? Discussion:

As shown in the figure above, the answer with the highest vote rate is to use enumeration.

The respondents quoted the views clearly expressed by Joshua Bloch in Effective Java:

Although the method of using enumeration to realize Singleton has not been widely used, the enumeration type of single element has become the best method to realize Singleton.

If you really have a deep understanding of the usage of singleton and some possible pits, you may also get the same conclusion: using enumeration to implement singleton is a good method.

Enumeration singleton is easy to write

The code of implementing singleton in various ways is relatively complex. The main reason is to consider thread safety.

Let's simply compare the code of "double check lock" method and enumeration method.

Implementation example of "double check lock":

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}  

Enumeration implementation singleton:

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}  

In contrast, you will find that the code for enumerating and implementing singletons will be much simpler.

The reason why the above double lock verification code is very bloated is that most of the code is to ensure thread safety. In order to make a trade-off between thread safety and lock granularity, the code will inevitably be more complex. However, there is still a problem with this code, because it cannot solve the problem that deserialization will destroy the singleton.

Enumeration can solve thread safety problems

Mentioned above. When using non enumeration to implement singleton, you have to ensure thread safety. Therefore, other methods must be bloated. So why not solve the thread safety problem when using enumeration?

In fact, it is not that using enumeration does not need to ensure thread safety, but the guarantee of thread safety does not need our concern. In other words, in fact, thread safety is guaranteed at the "bottom".

So what exactly does "bottom" mean?

This brings us to the implementation of enumeration. Here I will briefly explain:

Enum, like class, is a keyword in Java when defining enumeration. Just as a class class is applied to a class pair, enum also has an enum class.

By decompiling the defined enumeration, we can find that after javac compilation, the enumeration will be transformed into the definition of public final class T extensions enum.

Moreover, each enumeration item in the enumeration is defined by static at the same time. For example:

public enum T {
    SPRING,SUMMER,AUTUMN,WINTER;
}

The decompiled code is:

public final class T extends Enum
{
    //Omit some contents
    public static final T SPRING;
    public static final T SUMMER;
    public static final T AUTUMN;
    public static final T WINTER;
    private static final T ENUM$VALUES[];
    static
    {
        SPRING = new T("SPRING", 0);
        SUMMER = new T("SUMMER", 1);
        AUTUMN = new T("AUTUMN", 2);
        WINTER = new T("WINTER", 3);
        ENUM$VALUES = (new T[] {
            SPRING, SUMMER, AUTUMN, WINTER
        });
    }
}

Friends who understand the class loading mechanism of JVM should be clear about this part. The static type attribute will be initialized after the class is loaded. When a Java class is actually used for the first time, the static resources will be initialized, and the loading and initialization process of the Java class are thread safe (because when the virtual machine loads the enumerated classes, it will use the loadClass method of ClassLoader, which uses synchronous code blocks to ensure thread safety). Therefore, creating an enum type is thread safe.

In other words, an enumeration defined by us will be loaded and initialized by the virtual machine when it is actually used for the first time, and this initialization process is thread safe. As we know, to solve the concurrency problem of singleton, the main solution is the thread safety problem in the initialization process.

Therefore, due to the above characteristics of enumeration, the singleton implemented by enumeration is inherently thread safe.

Enumeration solves the problem that deserialization destroys singletons

As mentioned earlier, there are some problems with the singleton implemented by using double check lock, which may be destroyed by serialization lock.

So, for serialization, why does enumeration have inherent advantages? The serialization of enumeration is specified as follows:

When serializing, Java only outputs the name attribute of the enumerated object to the result, and when deserializing, it uses Java Lang. enum's valueOf method to find enumeration objects by name. At the same time, the compiler does not allow any customization of this serialization mechanism, so the methods such as writeObject, readObject, readObjectNoData, writeReplace and readResolve are disabled.

During the deserialization of a normal Java class, the object is initialized by calling the default constructor of the class through reflection. Therefore, even if the constructor in the singleton is private, it will be reflected and destroyed. Since the deserialized object is new, this destroys the singleton.

However, the deserialization of enumerations is not achieved through reflection. Therefore, there will be no single instance corruption caused by deserialization. This part also has a more detailed introduction to the in-depth analysis of Java enumeration types - Enumeration thread safety and serialization. It also shows some code, which can be read by interested friends.

summary
Among all singleton implementations, enumeration is the simplest way to write code. The reason why the code is very concise is that Java provides us with enum keyword, so we can easily declare an enumeration type without worrying about thread safety in its initialization process, Because the enumeration class will ensure that the thread is initialized safely when it is loaded by the virtual machine.

In addition, in terms of serialization, Java clearly stipulates that the serialization and deserialization of enumeration are specially customized. This can avoid the single instance corruption caused by reflection in the process of deserialization.

Enum class

Enum keyword is used to define enumeration in Java, but there is another Java Lang. enum class. This is an abstract class, which is defined as follows:

package java.lang;

public abstract class Enum<E extends Enum<E>> implements Constable, Comparable<E>, Serializable {
    private final String name;
    private final int ordinal;

}

We won't use this class in daily development, but in fact, we use enum defined enumeration, which is implemented by inheriting enum class.

When we use enmu to define an enumeration type, the compiler will automatically help us create a class of final type to inherit the Enum class, so the enumeration type cannot be inherited.

How to compare Java enumerations

There is no difference between the = = method and the equals method for comparing java enumeration values. Both of them have the same effect.

Because the default implementation of equals method of Enum class is to compare through = =;

The compareTo method of similar Enum compares the ordinal order size of Enum;

Similarly, the name method of Enum, like the toString method, returns the name value of Enum.

switch support for enumeration

Before Java 1.7, the available types of switch parameters are short, byte, int and char. The reason why enumeration types can be used is actually realized at the compiler level

The compiler converts the enumeration switch to something similar

switch(s.ordinal()) { 
    case Status.START.ordinal() 
}
 

Form, so the essence is the type of int parameter. If you are interested, you can write your own switch code using enumeration, and then look at the bytecode through javap -v.

How to realize the serialization of enumeration

How does enumeration ensure thread safety

If you want to see the source code, you must first have a class. What kind of enumeration type is it? Is it enum? Obviously, the answer is No. enum is just a keyword like class. It is not a class. So what class maintains enumeration? Let's simply write an enumeration:

public enum t {
    SPRING,SUMMER,AUTUMN,WINTER;
}

Then we use decompilation to see how this code is implemented. The content of the decompilated code is as follows:

public final class T extends Enum
{
    private T(String s, int i)
    {
        super(s, i);
    }
    public static T[] values()
    {
        T at[];
        int i;
        T at1[];
        System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
        return at1;
    }

    public static T valueOf(String s)
    {
        return (T)Enum.valueOf(demo/T, s);
    }

    public static final T SPRING;
    public static final T SUMMER;
    public static final T AUTUMN;
    public static final T WINTER;
    private static final T ENUM$VALUES[];
    static
    {
        SPRING = new T("SPRING", 0);
        SUMMER = new T("SUMMER", 1);
        AUTUMN = new T("AUTUMN", 2);
        WINTER = new T("WINTER", 3);
        ENUM$VALUES = (new T[] {
            SPRING, SUMMER, AUTUMN, WINTER
        });
    }
}

Through the decompiled code, we can see that the public final class T extends Enum indicates that this class inherits the enum class. At the same time, the final keyword tells us that this class cannot be inherited. When we use enum to define an enumeration type, the compiler will automatically help us create a class of final type to inherit enum class, so the enumeration type cannot be inherited. We see that there are several properties and methods in this class.

We can see:

        public static final T SPRING;
        public static final T SUMMER;
        public static final T AUTUMN;
        public static final T WINTER;
        private static final T ENUM$VALUES[];
        static
        {
            SPRING = new T("SPRING", 0);
            SUMMER = new T("SUMMER", 1);
            AUTUMN = new T("AUTUMN", 2);
            WINTER = new T("WINTER", 3);
            ENUM$VALUES = (new T[] {
                SPRING, SUMMER, AUTUMN, WINTER
            });
        }

They are all static type, because static type properties will be initialized after the class is loaded. When a Java class is really used for the first time, static resources are initialized, and the loading and initialization process of Java classes are thread safe. Therefore, creating an enum type is thread safe.

Why is singleton implemented by enumeration the best way

There are seven ways to implement singleton. Among them, Josh Bloch, author of Effective Java, advocates the use of enumeration. Since the great God says this method is good, we need to know why it is good?

1. Enumeration is easy to write

public enum EasySingleton{
    INSTANCE;
}

You can use easysingleton Instance to access.

2. Handle serialization by enumerating

We know that all previous singleton patterns have a big problem, that is, once the Serializable interface is implemented, it is no longer singleton, because every time the readObject() method is called, a newly created object is returned. One solution is to use the readResolve() method to avoid this. However, in order to ensure that each enumeration type and its defined enumeration variables are unique in the JVM, as stated in the Java specification, Java has made special provisions on the serialization and deserialization of enumeration types. The original text is as follows:

Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant's enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored–all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.

In other words, when serializing, Java only outputs the name attribute of the enumeration object to the result, and when deserializing, Java Lang. enum's valueOf method to find enumeration objects by name. At the same time, the compiler does not allow any customization of this serialization mechanism, so the methods such as writeObject, readObject, readObjectNoData, writeReplace and readResolve are disabled. Let's take a look at the valueOf method:

public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {  
            T result = enumType.enumConstantDirectory().get(name);  
            if (result != null)  
                return result;  
            if (name == null)  
                throw new NullPointerException("Name is null");  
            throw new IllegalArgumentException(  
                "No enum const " + enumType +"." + name);  
        }  

As you can see from the code, the code will try to get the enumeration object named name from the map returned by calling the enumConstantDirectory() method of the Class object enumType. If it does not exist, an exception will be thrown. The compiler will use the method of constantvalue () to create the attribute of constantvalue () in the above method, and then we will see that the result of calling constantvalue () will be filled in with the attribute of constantvalue () in the above method.

Therefore, the JVM guarantees serialization.

3. Enumeration instance creation is thread safe

When a Java class is actually used for the first time, the static resources are initialized, and the loading and initialization of Java classes are thread safe. Therefore, creating an enum type is thread safe.

Thread safety issues with enumerations

How does enumeration ensure thread safety

If you want to see the source code, you must first have a class. What kind of enumeration type is it? Is it enum? Obviously, the answer is No. enum is just a keyword like class. It is not a class. So what class maintains enumeration? Let's simply write an enumeration:

public enum t {
    SPRING,SUMMER,AUTUMN,WINTER;
}

Then we use decompilation to see how this code is implemented. The content of the decompilated code is as follows:

public final class T extends Enum
{
    private T(String s, int i)
    {
        super(s, i);
    }
    public static T[] values()
    {
        T at[];
        int i;
        T at1[];
        System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
        return at1;
    }

    public static T valueOf(String s)
    {
        return (T)Enum.valueOf(demo/T, s);
    }

    public static final T SPRING;
    public static final T SUMMER;
    public static final T AUTUMN;
    public static final T WINTER;
    private static final T ENUM$VALUES[];
    static
    {
        SPRING = new T("SPRING", 0);
        SUMMER = new T("SUMMER", 1);
        AUTUMN = new T("AUTUMN", 2);
        WINTER = new T("WINTER", 3);
        ENUM$VALUES = (new T[] {
            SPRING, SUMMER, AUTUMN, WINTER
        });
    }
}

Through the decompiled code, we can see that the public final class T extends Enum indicates that this class inherits the Enum class. At the same time, the final keyword tells us that this class cannot be inherited. When we use enmu to define an enumeration type, the compiler will automatically help us create a class of final type to inherit the Enum class, so the enumeration type cannot be inherited. We see that there are several properties and methods in this class.

We can see:

        public static final T SPRING;
        public static final T SUMMER;
        public static final T AUTUMN;
        public static final T WINTER;
        private static final T ENUM$VALUES[];
        static
        {
            SPRING = new T("SPRING", 0);
            SUMMER = new T("SUMMER", 1);
            AUTUMN = new T("AUTUMN", 2);
            WINTER = new T("WINTER", 3);
            ENUM$VALUES = (new T[] {
                SPRING, SUMMER, AUTUMN, WINTER
            });
        }

They are all static type, because static type attributes will be initialized after the class is loaded. When a Java class is actually used for the first time, the static resources are initialized, and the loading and initialization process of Java class are thread safe. Therefore, creating an enum type is thread safe.

reference material

If you are studying pure Java, you can apply to join me or if you are about to learn pure Java: 735057581 , you can exchange and share any questions you have. I uploaded some study manuals, development tools, PDF documents, books and tutorials that I have sorted out in recent years. You can download them yourself if necessary. Welcome to study together!

Keywords: Java

Added by Valord on Sat, 19 Feb 2022 02:29:01 +0200