Java Engineer's road to success basic chapter java basic knowledge enumeration

Enumeration usage

1. background

Before the introduction of enumeration types in the java language, common patterns representing enumeration types declared a set of constants with int. Previously, we usually used the public static final 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 is called iint 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 safety. 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 codes are 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("Earth has no seasons");
                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 type insecurity
    }

The program getChineseSeason(Season.SpPRING) is our expected method of use. 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 when enumerations are used, I need to easily get String expressions of enumerations. 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 relies on String comparison operations, so this mode is not expected. From the two aspects of type security and program readability, the shortcomings of int and String enumeration patterns are exposed. Fortunately, since the Java1.5 release, another 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. definitions

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 types.

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

3. features

The Java statement for defining enumeration types is simple. It has the following features:

  1. Use keyword enum
  2. Type name, such as Season here
  3. A list of allowed values, such as the four seasons of spring, summer, autumn and winter defined above
  4. Enumerations can be defined in a single file or embedded in other Java classes

In addition to such basic requirements, users have other options

  1. Enumeration can implement one or more interfaces
  2. New variables can be defined
  3. New methods can be defined
  4. 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 codes are 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 to 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("Earth has no seasons " + 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();
    }
}

5. summary

So when should I use enumeration? Whenever a set of fixed constants is required, such as the number of days in a week, the four seasons of a year, etc. Or a collection of all the values that 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 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;  
        }  
    }  
}  

Usage 3: add a new method to the 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 regular 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 class is the enumeration type? 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. After decompilation, the code contents are 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
        });
    }
}

Through decompiling the code, we can see that publc 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 enum type, the compiler will automatically help us create a class of final type to inherit enum class, so the enum type cannot be inherited.

Enumeration and singleton mode

Which is the best way to write a singleton

Through online voting, the answer with the highest vote rate is to use enumeration.

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

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

Enumeration singleton is easy to write

Let's make a simple comparison between the "double check lock" method and the 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 implementation singletons is 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 solves thread safety problems

Mentioned above. When using non enumeration methods to implement singletons, you should ensure thread safety by yourself. This leads to the fact that other methods are inevitably bloated. So why don't you solve the thread safety problem when using enumeration?

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

So what exactly does "bottom" mean?

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. 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 JVM's class loading mechanism 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 Java class loading and initialization process are thread safe (because the virtual machine will use the loadClass method of ClassLoader when loading enumerated classes, and this method 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 a singleton, we mainly solve 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 we mentioned earlier, the singleton implemented with double check lock actually has some problems, that is, this singleton may be destroyed by serialization lock.

So, why does enumeration have inherent advantages in serialization? The answer can be found in the Java Object Serialization Specification. The serialization of enumeration is specifically specified as follows:
The general meaning is: when serializing, Java only outputs the name attribute of the enumerated 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 writeObject, readObject, readObjectNoData, writeReplace, readResolve and other methods are disabled.

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

However, the deserialization of enumerations is not implemented through reflection. Therefore, the singleton destruction caused by deserialization will not occur.

summary

Among all the singleton implementation methods, 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 the thread safety in its initialization process, because the enumeration class will ensure that the thread can be safely initialized when it is loaded by the virtual machine.

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

How Java enumerations compare

There is no difference between the = = method and the equals method for java enumeration comparison. The two methods are the same if used casually.

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

A similar compareTo method of 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 enumerations

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

The compiler converts the enumeration switch to something like

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

Form, so the essence is an int parameter type. Where ordinal() returns the ordinal of the enumeration.

Tags: Java

Posted by siobhan on Tue, 31 May 2022 06:17:43 +0530