Java enums you may not understand

Enumerations are also an old talk in Java. Whenever we encounter a set of data that needs to be enumerated, we will naturally use enumeration types:

public enum Color {
RED, GREEN, BLUE, YELLOW;

public static void main(String[] args) {
    Color red = Color.RED;
    Color redAnother = Color.RED;
    Color blue = Color.BLUE;

    System.out.println(red.equals(redAnother)); // true
    System.out.println(red.equals(blue)); // false
}

}
Of course, what we are going to discuss today is not the basic syntax of enum in java. This topic will go into the essence of enum and discuss some high-level usage. This article is based on Oracle JDK 14.0.2 and jad v1.5.8e (because jad has not been updated for a long time, the support for the new version of jdk is not very complete, but simply analyzing enum and interface is enough).

The Secret Behind Custom Enumeration Values ​​The
default value of an enumeration is a value that increases from 0, which is usually sufficient. However, Java also allows us to personalize the enumeration value, for example:

// We not only want to use the orientation in English, but also want to obtain the corresponding localized name (here in Chinese)
enum Direction { EAST(“东”), WEST(“西”), NORTH(“北”), SOUTH( "south");



private final String name;

// 注意是private
private Direction(String name) {
    this.name = name;
}

public String getName() {
    return this.name;
}

}

public class Test { public static void main(String[] args) { for (var v: Direction.values()) { System.out.println(v.toString() + “–>” + v.getName()) ; } } } Compile and run the program, you will get the following result:






EAST–>East
WEST–>West
NORTH–>North
SOUTH–>South.
This is the end of many tutorials. So far, what is the effect of the parentheses after the enumeration value? Why the constructor requires private modification? No explanation is given. However, understanding these is the premise for us to further study the advanced usage of enumeration.

But it doesn't matter, we can explore it ourselves, for example, look at the decompiled code, and explore the way the compiler handles enumeration types. Here we will use jad, and refer to other excellent articles in the park for specific usage tutorials. This article will not go into details, we will directly look at the results after decompilation:

final class Direction extends Enum
{

/* 省略部分无关紧要的方法 */

private Direction(String s, int i, String s1)
{
    super(s, i);
    name = s1;
}

public String getName() // 这是我们自定义的getter
{
    return name;
}

public static final Direction EAST;
public static final Direction WEST;
public static final Direction NORTH;
public static final Direction SOUTH;
private final String name;
// 省略不重要的部分字段

static 
{
    EAST = new Direction("EAST", 0, "\u4E1C");
    WEST = new Direction("WEST", 1, "\u897F");
    NORTH = new Direction("NORTH", 2, "\u5317");
    SOUTH = new Direction("SOUTH", 3, "\u5357");
    // 省略部分字段的初始化
}

}
First of all, we see that our enum is a class, and secondly it inherits from java.lang.Enum (which means that enum cannot explicitly specify the base class), and we call the structure of its parent class in the constructor of Direction Function, by reading the document, we can see that the constructor of java.lang.Enum is protected, which means that users outside the java.lang package cannot call this constructor. At the same time the document also pointed out that the constructor is automatically called by the compiler. Therefore, the enum constructor defined by ourselves cannot be called normally, and can only be used by the compiler to initialize enum members. Since it cannot be called by the user, Java simply does not allow protected and public (default and private allow) to modify the constructor of the custom enum type to avoid misuse.

In addition, our custom constructor is actually synthesized by the compiler. In addition to the custom parameters, there are the string names of enumeration members and a serial number starting from 0 (can be obtained by the ordinal method). The first two parameters are compiled The device will be added for us automatically, and the custom parameter is passed to the constructor based on the value in the parentheses after the enumeration member we give, in short:

EAST("East"),
WEST("西"),
NORTH("北"),
SOUTH("南");

// Converted to (unicode characters are transcoded)
EAST = new Direction(“EAST”, 0, “\u4E1C”);
WEST = new Direction(“WEST”, 1, “\u897F”);
NORTH = new Direction( "NORTH", 2, "\u5317");
SOUTH = new Direction("SOUTH", 3, "\u5357");
If I need more fields, just like this:

public enum Planet { // With two custom values MERCURY (3.303e+23, 2.4397e6), VENUS (4.869e+24, 6.0518e6), EARTH (5.976e+24, 6.37814e6), MARS (6.421e +23, 3.3972e6), JUPITER (1.9e+27, 7.1492e7), SATURN (5.688e+26, 6.0268e7), URANUS (8.686e+25, 2.5559e7), NEPTUNE (1.024e+26, 2.4746e7) ;








// 保存自定义值的字段,不使用final也可以,但枚举值一般不应该发生改变
private final double mass;   // in kilograms
private final double radius; // in meters
// 在这里使用default的权限控制,即package-private
Planet(double mass, double radius) {
    this.mass = mass;
    this.radius = radius;
}
public double mass() { return mass; }
public double radius() { return radius; }

}
This is the secret behind custom enumeration values.

So far our questions have almost been answered, but careful observation will reveal that our enumeration members are all Direction's _static fields_! Therefore, we cannot use these enumeration members as types:

public void work(Direction.EAST e) { // This cannot be compiled } The static field is well understood, because we need to use the class name + enumeration member name Direction.WEST to directly reference, but why should the field type be Direction? ?


Don't worry, the answer will be revealed in the next section.

Adding abstract methods to enumerations
This section seems ridiculous. Abstract methods seem to be incompatible with enumeration. But think about it carefully. In the previous section, we have added a getter member method to the enumeration. This shows that we can also add other methods to the enumeration to customize the behavior of the enumeration type. The Planet in the previous section is an example. We can add the calculation of the gravity and mass of any object on the surface of a planet:

public enum Planet {

/* 定义枚举成员和初始化的相关重复代码,此处不再重复 */

private double mass() { return mass; }
private double radius() { return radius; }

// universal gravitational constant  (m3 kg-1 s-2)
public static final double G = 6.67300E-11;

double surfaceGravity() {
    return G * mass / (radius * radius);
}
double surfaceWeight(double otherMass) {
    return otherMass * surfaceGravity();
}
public static void main(String[] args) {
    if (args.length != 1) {
        System.err.println("Usage: java Planet <earth_weight>");
        System.exit(-1);
    }
    double earthWeight = Double.parseDouble(args[0]);
    double mass = earthWeight/EARTH.surfaceGravity();
    for (Planet p : Planet.values())
       System.out.printf("Your weight on %s is %f%n",
                         p, p.surfaceWeight(mass));
}

}The
running results are as follows:

$ java Planet.java 70

Your weight ON MERCURY IS 26.443033
Your weight ON VENUS IS 63.349937
Your weight ON EARTH IS 70.000000
Your weight ON MARS IS 26.511603
Your weight ON JUPITER IS 177.139027
Your weight ON SATURN IS 74.621088
Your weight ON URANUS IS 63.358904
Your weight ON NEPTUNE IS 79.682965
given that they Customizing the behavior of the entire enum, does that mean that we can define the behavior of the enumeration members separately, after all, the method is ultimately called from the enumeration member value.

The answer is yes. Remember how the compiler handled enumeration members in the last part of the previous section?

EAST = new Direction(“EAST”, 0, “\u4E1C”);
WEST = new Direction(“WEST”, 1, “\u897F”);
NORTH = new Direction(“NORTH”, 2, “\u5317”) ;
SOUTH = new Direction("SOUTH", 3, "\u5357");
Yes, the enumeration member itself is also an instance of the enum object! And although these enumeration members are of type Direction, they can actually refer to derived types of Direction.

Suppose we have an enumeration of the Color type, and for each enumeration member we have a custom print method for printing different information:

enum Color { RED{ // Don't worry about the syntax for now, I will explain later @Override public void print() { // Output a color string on Linux System.out.println("\u001B[1;31m This is red text \u001B[0m"); } }, BLUE{ @Override public void print() { System.out.println("\u001B[1;34m This is blue text \u001B[0m"); } }, GREEN{ @Override public void print() { System.out.println("\u001B[1;32m This is green text \u001B[0m"); } };



















// 枚举成员必须要覆写的抽象方法
public abstract void print();

}

public class Test { public static void main(String[] args) { for (var v: Color.values()) { v.print(); } } } The running results are as follows:






To know the principle, we still have to use jad, which is the processed content of Color.class:

// becomes an abstract class
abstract class Color extends Enum
{ // constructor private Color(String s, int i) { super(s, i); }




public abstract void print();

public static final Color RED;
public static final Color BLUE;
public static final Color GREEN;

static 
{
    // 重点从这开始
    RED = new Color("RED", 0) {

        public void print()
        {
            System.out.println("\033[1;31m This is red text \033[0m");
        }

    };
    BLUE = new Color("BLUE", 1) {

        public void print()
        {
            System.out.println("\033[1;34m This is blue text \033[0m");
        }

    };
    GREEN = new Color("GREEN", 2) {

        public void print()
        {
            System.out.println("\033[1;32m This is green text \033[0m");
        }

    };
}

}
Careful readers have probably found out, isn’t this _anonymous inner class_? That's right, our enum type has actually become an abstract class this time, and the enumeration members are anonymous inner classes that inherit from Color and implement abstract methods. So the braces we marked with comments at the beginning can actually be understood as the class body of the anonymous class. However, it should be noted that although new is explicitly used here to create an anonymous inner class, the constructor is still called by the compiler.

What if you want to add custom enumerated data? You can do this:

enum Color {
RED(31){
@Override
public void print() {
System.out.println("\u001B[1;31m This is red text \u001B[0m");
}
},
BLUE(34){
@Override
public void print() {
System.out.println("\u001B[1;34m This is blue text \u001B[0m");
}
},
GREEN(32){
@Override
public void print() {
System.out.println("\u001B[1;32m This is green text \u001B[0m");
}
};

// color code
private final int colorCode;
private Color(int code) {
    colorCode = code;
}
public int getColorCode() {
    return colorCode;
}

public abstract void print();

}
Let’s take a look at the compiled code. Due to space limitations, I only kept the important part:

abstract class Color extends Enum
{

/* 大量省略代码 */

private Color(String s, int i, int j)
{
    super(s, i);
    colorCode = j;
}

public abstract void print();

public static final Color RED;
public static final Color BLUE;
public static final Color GREEN;
private final int colorCode;

static 
{
    // 参数传递给了构造函数
    RED = new Color("RED", 0, 31) {

        public void print()
        {
            System.out.println("\033[1;31m This is red text \033[0m");
        }

    };
    BLUE = new Color("BLUE", 1, 34) {

        public void print()
        {
            System.out.println("\033[1;34m This is blue text \033[0m");
        }

    };
    GREEN = new Color("GREEN", 2, 32) {

        public void print()
        {
            System.out.println("\033[1;32m This is green text \033[0m");
        }

    };
}

}
To sum up, for an enum type, there is usually the following format:

[public] enum NAME [implements XXX, …] { VALUE1 [(Custom data, the format is the same as the parameter list of custom constructor function)] [{ // You can override or add new methods }], …, VALUEN [(…)] [{ // overrides or methods }];








[存储各种自定义数据的字段,最好用final修饰]
[
    // 自定义构造函数
    [private] NAME(和枚举成员中给出的圆括号内的内容一致) { /* 设置数据字段 */ }
]

[定义抽象方法或者重写object/Enum的方法或是添加普通类方法]

}
The part enclosed by [] in the format given can be omitted.

Enumeration and interface
At the end of the previous section, we saw that enum can actually implement interface (after all, it is essentially a class), so the example in the previous section can be written like this:

interface Printer {
void print();
}

enum Color implements Printer { RED{ @Override public void print() { System.out.println("\u001B[1;31m This is red text \u001B[0m"); } }, BLUE{ @Override public void print( ) { System.out.println("\u001B[1;34m This is blue text \u001B[0m"); } }, GREEN{ @Override public void print() { System.out.println("\u001B[1 ;32m This is green text \u001B[0m"); } }; } I personally prefer the second method, because enum is mainly a collection of data, and the behavior/pattern exhibited by the data is described using interface as much as possible.



















In addition, enum can also be defined in iinterface. Suppose we have an enumeration representing from Monday to Sunday, and a method isRestDay is given to determine whether the current date can be rested (for example, some people have weekends and some people have a single day off, and some people have a rest on Monday or Friday). Types of people will have different answers to the day of the week, so it is more appropriate to abstract it as an interface:

interface Relaxable {
enum Weekly {
Mon, Tue, Wed, Thu, Fri, Sat, Sun
}

boolean isRestDay(Relaxable.Weekly day);

}

class PersonA implements Relaxable {
@Override
public boolean isRestDay(Relaxable.Weekly day) {
return day.equals(Relaxable.Weekly.Sat) || day.equals(Relaxable.Weekly.Sun);
}
}

class PersonB implements Relaxable {
@Override
public boolean isRestDay(Relaxable.Weekly day) {
return day.equals(Relaxable.Weekly.Sun);
}
}

public class Relax { public static void main(String[] args) { var a = new PersonA(); var b = new PersonB(); var day = Relaxable.Weekly.Sat; System.out.println(a.isRestDay( day)); // true System.out.println(b.isRestDay(day)); // false } } PersonA has a nice weekend, and poor PersonB has to work overtime on Saturday! Use jad to view the produced code:








interface Relaxable
{
public static final class Weekly extends Enum
{

    /* 省略了部分代码 */

    public static final Weekly Mon;
    public static final Weekly Tue;
    public static final Weekly Wed;
    public static final Weekly Thu;
    public static final Weekly Fri;
    public static final Weekly Sat;
    public static final Weekly Sun;

    static 
    {
        Mon = new Weekly("Mon", 0);
        Tue = new Weekly("Tue", 1);
        Wed = new Weekly("Wed", 2);
        Thu = new Weekly("Thu", 3);
        Fri = new Weekly("Fri", 4);
        Sat = new Weekly("Sat", 5);
        Sun = new Weekly("Sun", 6);
    }

    private Weekly(String s, int i)
    {
        super(s, i);
    }
}

public abstract boolean isRestDay(Weekly weekly);

}
Amazon evaluation www.yisuping.com

Guess you like

Origin blog.csdn.net/weixin_45032957/article/details/108511126