This article is excerpted in part from On Java 8

Enumerated type

An enum keyword has been added to Java5. With the enum keyword, we can create a new type from a limited set of named values that can be used as regular program components, for example:

public enum Spiciness {
    NOT, MILD, MEDIUM, HOT, FLAMING
}

Copy the code

Here you create an enumeration type called Spiciness that has five values. Because instances of enumerated types are constants, they are all capitalized by naming conventions (if the name contains more than one word, they are separated by underscores)

To use enum, create a reference to that type and assign it to an instance:

public class SimpleEnumUse {
    public static void main(String[] args) { Spiciness howHot = Spiciness.MEDIUM; System.out.println(howHot); }}// Output: MEDIUM
Copy the code

The use of enUms in switch is a very convenient feature that enUms provide. In general, only integer values can be used ina switch, and enumeration instances have an innate order of integer values, which can be obtained by the ordinal() method, so we can use enums in switch statements

Normally we must use enum types to decorate an enum instance, but this is not necessary in case statements. The following example constructs a simulated traffic light state change using enum:

enum Signal { GREEN, YELLOW, RED, }

public class TrafficLight {
    
    Signal color = Signal.RED;
    
    public void change(a) {
        switch(color) {
            case RED: color = Signal.GREEN;
                break;
            case GREEN: color = Signal.YELLOW;
                break;
            case YELLOW: color = Signal.RED;
                break; }}@Override
    public String toString(a) {
        return "The traffic light is " + color;
    }
    
    public static void main(String[] args) {
        TrafficLight t = new TrafficLight();
        for(int i = 0; i < 7; i++) { System.out.println(t); t.change(); }}}Copy the code

Basic features of enumerations

Every enumeration in Java inherits from the java.lang.Enum class, and all instances of enumeration can call the methods of the Enum class

Calling the values() method of enum returns an array of instances of enum in the exact order in which the elements were declared in the enum, so you can use the array returned by values() in a loop

enum Shrubbery { GROUND, CRAWLING, HANGING }
public class EnumClass {
    public static void main(String[] args) {
        for(Shrubbery s : Shrubbery.values()) {
            System.out.println(s);
            // Returns the order in which each enumerated instance was declared
            System.out.println(s.ordinal());
            // Returns the Class object corresponding to the enumeration type of this enumeration constant
            System.out.println(s.getDeclaringClass());
            // Returns the name of the enumeration instance declared, equivalent to printing directlySystem.out.println(s.name()); . }}}/ / output:
// GROUND
/ / 0
// GROUND
// CRAWLING
/ / 1
// CRAWLING
// HANGING
/ / 2
// HANGING
Copy the code

You can compare enum instances using ==, and the compiler will automatically provide you with equals() and hashCode() methods. At the same time, the Enum class implements the Comparable interface, so it has the compareTo() method, and at the same time, it implements the Serializable interface

The ValueOf() method is a static method defined in an Enum that returns an instance of the Enum given the name and throws an exception if none exists

Shrubbery shrub = Enum.valueOf(Shrubbery.class, "HANGING");
Copy the code

Let’s look at the values() method, why do we say that? As mentioned earlier, the enum classes that the compiler creates for you inherit from the enum class. However, if you look at the Enum class, you will see that it has no values() method. But we have already used this method, is it hidden? The answer is that values() is a static method added by the compiler. The compiler also marks the created enumeration class as static final, so it cannot be inherited

Since the values() method is static inserted into the enum definition by the compiler, if you cast an enum instance up to an enum, the values() method is not available. However, there is one getEnumConstants() method in Class, so even if there is no values() method in the Enum interface, we can still get all Enum instances through the Class object

enum Search { HITHER, YON }
public class UpcastEnum {
    public static void main(String[] args) {
        for(Enum en : e.getClass().getEnumConstants()) System.out.println(en); }}Copy the code

Because getEnumConstants() is a method on Class, you can call this method even on classes that aren’t enumerations, except that it returns NULL

Method to add

Except that we cannot inherit from an enum, we can basically think of an enum as a regular class. That is, we can add methods to the enum. Enum can even have a main() method

We want each enumerated instance to be able to return a description of itself, not just the default toString() implementation, which only returns the name of the enumerated instance. To do this, you can provide a constructor that handles the extra information and then add a method that returns the description. Take a look at the following example:

public enum OzWitch {
    
    WEST("Miss Gulch, aka the Wicked Witch of the West"),
    NORTH("Glinda, the Good Witch of the North"),
    EAST("Wicked Witch of the East, wearer of the Ruby "),
    SOUTH("Good by inference, but missing");	// A semicolon must be added at the end of the enum instance sequence
    
    private String description;
    
    private OzWitch(String description) {
        this.description = description;
    }
    
    public String getDescription(a) { return description; }
    
    public static void main(String[] args) {
        for(OzWitch witch : OzWitch.values())
            System.out.println(witch + ":"+ witch.getDescription()); }}Copy the code

In this example, we consciously declared the enum’s constructor private, but there is no change in its accessibility because (even without declaring it private) we can only use its constructor to create enum instances inside the enum definition. Once the definition of enum is complete, the compiler does not allow us to use its constructor to create any more instances

If you want to override a method in an enum, such as the toString() method, it is no different from overriding a generic class:

public enum SpaceShip {
    
    SCOUT, CARGO, TRANSPORT,
    CRUISER, BATTLESHIP, MOTHERSHIP;
    
    @Override
    public String toString(a) {
        String id = name();
        String lower = id.substring(1).toLowerCase();
        return id.charAt(0) + lower;
    }
    
    public static void main(String[] args) { Stream.of(values()).forEach(System.out::println); }}Copy the code

Implementing an interface

We already know that all enUms inherit from the java.lang. enum class. Because Java does not support multiple inheritance, enUms can no longer inherit from other classes, but can implement one or more interfaces

enum CartoonCharacter implements Supplier<CartoonCharacter> {
    
    SLAPPY, SPANKY, PUNCHY,
    SILLY, BOUNCY, NUTTY, BOB;
    
    private Random rand = new Random(47);
    
    @Override
    public CartoonCharacter get(a) {
        returnvalues()[rand.nextInt(values().length)]; }}Copy the code

By implementing a feeding interface, you can get a random enumerated value by calling the get() method. We can use generics to make the task of random selection more general and become a general utility class

public class Enums {
    
    private static Random rand = new Random(47);

    public static <T extends Enum<T>> T random(Class<T> ec) {
        return random(ec.getEnumConstants());
    }

    public static <T> T random(T[] values) {
        returnvalues[rand.nextInt(values.length)]; }}Copy the code

<T extends Enum> indicates that T is an Enum instance. With Class as an argument, we can use the Class object to get an array of enum instances. The overloaded random() method simply takes T[] as an argument, because it does not call any operations on the Enum. It simply selects a random element from the array. Thus, the final return type is exactly the type of enum

Using interfaces can help us achieve the purpose of classifying enumeration elements. For example, suppose you wanted to use enums to represent different categories of Food, while still wanting each enum element to be of the Food type:

public interface Food {
    
    enum Appetizer implements Food {
        SALAD, SOUP, SPRING_ROLLS;
    }
    
    enum MainCourse implements Food {
        LASAGNE, BURRITO, PAD_THAI,LENTILS, HUMMOUS, VINDALOO;
    }
    
    enum Dessert implements Food {
        TIRAMISU, GELATO, BLACK_FOREST_CAKE,FRUIT, CREME_CARAMEL;
    }
    
    enum Coffee implements Food { BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,LATTE, CAPPUCCINO, TEA, HERB_TEA; }}Copy the code

Implementing the interface is the only way to subclass an enum, so each enum embedded in Food implements the Food interface. Now, in the following program, we can say “Everything is some type of Food”

public class TypeOfFood {
    
    public static void main(String[] args) { Food food = Appetizer.SALAD; food = MainCourse.LASAGNE; food = Dessert.GELATO; food = Coffee.CAPPUCCINO; }}Copy the code

If the number of enUms is too high, the amount of code in an interface can be large. We can use the getEnumConstants() method to get all enum instances of a Food subclass based on a Class object

public enum Course {
    
    APPETIZER(Food.Appetizer.class),
    MAINCOURSE(Food.MainCourse.class),
    DESSERT(Food.Dessert.class),
    COFFEE(Food.Coffee.class);
    
    private Food[] values;
    
    private Course(Class<? extends Food> kind) {
        values = kind.getEnumConstants();
    }
    
    // Randomly get an enum instance of the Food subclass
    public Food randomSelection(a) {
        returnEnums.random(values); }}Copy the code

If you are familiar with internal classes, you can also use a more concise way of managing enumerations by nesting the interfaces in the enumerations in the first way, giving the code a cleaner structure

enum SecurityCategory {
    
    STOCK(Security.Stock.class),
    BOND(Security.Bond.class);
    
    Security[] values;
    
    SecurityCategory(Class<? extends Security> kind) {
        values = kind.getEnumConstants();
    }
    
    interface Security {
        enum Stock implements Security {
            SHORT, LONG, MARGIN
        }
        enum Bond implements Security {
            MUNICIPAL, JUNK
        }
    }
    
    public Security randomSelection(a) {
        return Enums.random(values);
    }
    
    public static void main(String[] args) {
        for(int i = 0; i < 10; i++) {
            SecurityCategory category = Enums.random(SecurityCategory.class);
            System.out.println(category + ":"+ category.randomSelection()); }}}Copy the code

EnumSet

EnumSet is a collection that operates on enUms. It can be used to store enumeration constants of the same enumeration type. The order in which the elements are stored depends on the order in which the Enum instance was defined. EnumSet was designed to replace the traditional int-based “bit flags.” Traditional “bit flags” can be used to indicate some kind of “on/off” information, but with these flags we end up manipulating bits rather than the concept they’re trying to express, making it easy to write confusing code

Since EnumSet is replacing the bit flag, its performance should be as efficient as using the bit flag. The basis of EnumSet is long. A long has 64 bits, and an enum instance needs only one bit to indicate whether it exists. That is, your EnumSet can be applied to enUms with up to 64 elements, without exceeding the expressive power of one long. If the enum exceeds 64 elements, EnumSet adds a long if necessary

The methods of EnumSet are as follows:

methods role
allOf(Class elementType) Creates an EnumSet containing all enumeration values in the specified enumeration class
complementOf(EnumSet e) Create an EnumSet whose element type is the same as that of the specified EnumSet. The new EnumSet contains enumeration values that were not included in the original EnumSet
copyOf(Collection c) Use a normal collection to create the EnumSet collection
copyOf(EnumSet e) Creates a collection of Enumsets that specifies that enumsets have the same element type and the same set elements
noneOf(Class elementType) Creates an empty EnumSet whose element type is the specified enumeration type
First of (E, E… rest) Create an EnumSet containing one or more enumeration values that must belong to the same enumeration class
range(E from,E to) Create an EnumSet containing all enumerations in the range from enumeration values to enumeration values

Sample code:

public enum AlarmPoints {
    STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
    OFFICE4, BATHROOM, UTILITY, KITCHEN
}

public class EnumSets {
    public static void main(String[] args) { EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class); points.add(BATHROOM); System.out.println(points); points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN)); System.out.println(points); points = EnumSet.allOf(AlarmPoints.class); points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN)); System.out.println(points); points.removeAll(EnumSet.range(OFFICE1, OFFICE4)); System.out.println(points); points = EnumSet.complementOf(points); System.out.println(points); }}Copy the code

EnumMap

EnumMap is a special Map that requires the key to be an enumeration type and the value to be unlimited. The bottom layer of EnumMap is implemented by an even number group (one holds the key and the other holds the value). In addition, when the value is null, it is treated as an Object. As with EnumSet, the order in which elements are stored depends on the order in which the enum instance is defined

/ / the key type
private final Class<K> keyType;

/ / key array
private transient K[] keyUniverse;

/ / the value of the array
private transient Object[] vals;

// Number of key-value pairs
private transient int size = 0;

// Value Specifies the value when null is set
private static final Object NULL = new Object() {
    public int hashCode(a) {
        return 0;
    }
    public String toString(a) {
        return "java.util.EnumMap.NULL"; }};Copy the code

Because EnumMap can hold enumeration types, enumeration types must be specified during initialization, and EnumMap provides three constructors

// Creates an empty enumeration map with the specified key type
EnumMap(Class<K> keyType);
// Create an enumeration map of the same key type as the specified enumeration map, initially containing the same map (if any)
EnumMap(EnumMap<K,? extends V> m);
// Create an enumeration map initialized from the specified map
EnumMap(Map<K,? extends V> m);
Copy the code

Other than that, EnumMap does not operate differently from a regular Map. EnumMap has the advantage of allowing programmers to change value objects, while constant-dependent methods are fixed at compile time

Constant specific method

We can define one or more abstract methods for enum and then implement that abstract method for each enum instance

public enum ConstantSpecificMethod {
    DATE_TIME {
        @Override
        String getInfo(a) {
            return DateFormat.getDateInstance().format(new Date());
        }
    },
    CLASSPATH {
        @Override
        String getInfo(a) {
            return System.getenv("CLASSPATH");
        }
    },
    VERSION {
        @Override
        String getInfo(a) {
            return System.getProperty("java.version"); }};abstract String getInfo(a);
    public static void main(String[] args) {
        for(ConstantSpecificMethod csm : values()) System.out.println(csm.getInfo()); }}Copy the code

In object-oriented programming, different behaviors are associated with different classes. Through the constant-dependent approach, each enum instance can have its own unique behavior, which seems to indicate that each enum instance is like a unique class. In the above example, the enum instance appears to be used as its superclass ConstantSpecificMethod, which behaves polymorphic when the getInfo() method is called

However, the similarities between enum instances and classes end there. We cannot really use an enum instance as a type, because each enum element is a static final instance of a specified enumeration type

In addition to the abstract methods, programmers can override ordinary methods

public enum OverrideConstantSpecific {
    NUT, BOLT,
    WASHER {
        @Override
        void f(a) {
            System.out.println("Overridden method"); }};void f(a) {
        System.out.println("default behavior");
    }
    public static void main(String[] args) {
        for(OverrideConstantSpecific ocs : values()) {
            System.out.print(ocs + ":"); ocs.f(); }}}Copy the code

Multi-channel distribution

Plus (Number) = a.plus(b) = a.plus(b) = a.plus(b) = A. plus(b) Now you only know that a and B belong to the type Number. You don’t know what the numbers are, but they may be integers or floating point numbers. According to different Number types, the results of mathematical operations should be different.

If you know anything about Java polymorphism, the implementation of Java polymorphism relies on Java’s dynamic binding mechanism to discover the true type of an object at runtime. However, Java only supports single-path distribution, that is, if the operation to be performed contains more than one unknown object, then Java’s dynamic binding mechanism can only handle one of the types. A. Price (b) involves two types, which naturally cannot solve our problem, so we have to decide the other types ourselves

The solution to this problem is multi-channel distribution. The above example, since there are only two distributions, is generally referred to as two-channel distribution. Polymorphism can only occur on method calls, so if you want to use two-way distribution, you must have two method calls: the first method call determines the first unknown type, and the second method call determines the second unknown type. The programmer must set up some configuration so that one method call leads to other method calls, handling multiple types in the process

Here’s an example:

package enums;
public enum Outcome { WIN, LOSE, DRAW }	// The result of guessing: win, lose, draw
Copy the code
package enums;
import java.util.*;
import static enums.Outcome.*;	// Introduce enums so that the prefix Outcome is not used
interface Item {
    Outcome compete(Item it);
    Outcome eval(Paper p);
    Outcome eval(Scissors s);
    Outcome eval(Rock r);
}
class Paper implements Item {
    @Override
    public Outcome compete(Item it) {
        return it.eval(this);
    }
    @Override
    public Outcome eval(Paper p) { return DRAW; }
    @Override
    public Outcome eval(Scissors s) { return WIN; }
    @Override
    public Outcome eval(Rock r) { return LOSE; }
    @Override
    public String toString(a) { return "Paper"; }}class Scissors implements Item {
    @Override
    public Outcome compete(Item it) {
        return it.eval(this);
    }
    @Override
    public Outcome eval(Paper p) { return LOSE; }
    @Override
    public Outcome eval(Scissors s) { return DRAW; }
    @Override
    public Outcome eval(Rock r) { return WIN; }
    @Override
    public String toString(a) { return "Scissors"; }}class Rock implements Item {
    @Override
    public Outcome compete(Item it) {
        return it.eval(this);
    }
    @Override
    public Outcome eval(Paper p) { return WIN; }
    @Override
    public Outcome eval(Scissors s) { return LOSE; }
    @Override
    public Outcome eval(Rock r) { return DRAW; }
    @Override
    public String toString(a) { return "Rock"; }}public class RoShamBo1 {
    static final int SIZE = 20;
    private static Random rand = new Random(47);
    public static Item newItem(a) {
        switch(rand.nextInt(3)) {
            default:
            case 0: return new Scissors();
            case 1: return new Paper();
            case 2: return newRock(); }}public static void match(Item a, Item b) {
        System.out.println(
                a + " vs. " + b + ":" + a.compete(b));
    }
    public static void main(String[] args) {
        for(int i = 0; i < SIZE; i++) match(newItem(), newItem()); }}Copy the code

The above is the implementation of multipath distribution, its advantage is to avoid redundant code to determine the type of multiple objects, but the configuration process requires many procedures. Now that we’ve learned about enumerations, it’s natural to consider using them to optimize your code

public interface Competitor<T extends Competitor<T>> {
    Outcome compete(T competitor);
}

public class RoShamBo {
    
    public static <T extends Competitor<T>> void match(T a, T b) {
        System.out.println(a + " vs. " + b + ":" + a.compete(b));
    }
    
    public static <T extends Enum<T> & Competitor<T>> 
        void play(Class<T> rsbClass, int size) {
        for(int i = 0; i < size; i++) match(Enums.random(rsbClass),Enums.random(rsbClass)); }}public enum RoShamBo2 implements Competitor<RoShamBo2> {
    
    PAPER(DRAW, LOSE, WIN),
    SCISSORS(WIN, DRAW, LOSE),
    ROCK(LOSE, WIN, DRAW);
    
    private Outcome vPAPER, vSCISSORS, vROCK;
    
    RoShamBo2(Outcome paper, Outcome scissors, Outcome rock) {
        this.vPAPER = paper;
        this.vSCISSORS = scissors;
        this.vROCK = rock;
    }
    
    @Override
    public Outcome compete(RoShamBo2 it) {
        switch(it) {
            default:
            case PAPER: return vPAPER;
            case SCISSORS: return vSCISSORS;
            case ROCK: returnvROCK; }}public static void main(String[] args) {
        RoShamBo.play(RoShamBo2.class, 20); }}Copy the code

You can also use enum in switch statements

import static enums.Outcome.*;
public enum RoShamBo3 implements Competitor<RoShamBo3> {
    PAPER {
        @Override
        public Outcome compete(RoShamBo3 it) {
            switch(it) {
                default:
                case PAPER: return DRAW;
                case SCISSORS: return LOSE;
                case ROCK: return WIN;
            }
        }
    },
    SCISSORS {
        @Override
        public Outcome compete(RoShamBo3 it) {
            switch(it) {
                default:
                case PAPER: return WIN;
                case SCISSORS: return DRAW;
                case ROCK: return LOSE;
            }
        }
    },
    ROCK {
        @Override
        public Outcome compete(RoShamBo3 it) {
            switch(it) {
                default:
                case PAPER: return LOSE;
                case SCISSORS: return WIN;
                case ROCK: returnDRAW; }}};@Override
    public abstract Outcome compete(RoShamBo3 it);
    public static void main(String[] args) {
        RoShamBo.play(RoShamBo3.class, 20); }}Copy the code

The above code can be further compressed

public enum RoShamBo4 implements Competitor<RoShamBo4> {
    ROCK {
        @Override
        public Outcome compete(RoShamBo4 opponent) {
            return compete(SCISSORS, opponent);
        }
    },
    SCISSORS {
        @Override
        public Outcome compete(RoShamBo4 opponent) {
            return compete(PAPER, opponent);
        }
    },
    PAPER {
        @Override
        public Outcome compete(RoShamBo4 opponent) {
            returncompete(ROCK, opponent); }};Outcome compete(RoShamBo4 loser, RoShamBo4 opponent) {
        return ((opponent == this)? Outcome.DRAW : ((opponent == loser) ? Outcome.WIN : Outcome.LOSE)); }public static void main(String[] args) {
        RoShamBo.play(RoShamBo4.class, 20); }}Copy the code

True two-way distribution can be achieved using EnumMap. EnumMap is a special Map designed for enum with excellent performance. Since our goal is to explore two unknown types, we can use an EnumMap of EnumMap to implement two-way distribution:

package enums;
import java.util.*;
import static enums.Outcome.*;
enum RoShamBo5 implements Competitor<RoShamBo5> {
    PAPER, SCISSORS, ROCK;
    static EnumMap<RoShamBo5,EnumMap<RoShamBo5,Outcome>>
            table = new EnumMap<>(RoShamBo5.class);
    static {
        for(RoShamBo5 it : RoShamBo5.values())
            table.put(it, new EnumMap<>(RoShamBo5.class));
        initRow(PAPER, DRAW, LOSE, WIN);
        initRow(SCISSORS, WIN, DRAW, LOSE);
        initRow(ROCK, LOSE, WIN, DRAW);
    }
    static void initRow(RoShamBo5 it, Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {
        EnumMap<RoShamBo5,Outcome> row =
                RoShamBo5.table.get(it);
        row.put(RoShamBo5.PAPER, vPAPER);
        row.put(RoShamBo5.SCISSORS, vSCISSORS);
        row.put(RoShamBo5.ROCK, vROCK);
    }
    @Override
    public Outcome compete(RoShamBo5 it) {
        return table.get(this).get(it);
    }
    public static void main(String[] args) {
        RoShamBo.play(RoShamBo5.class, 20); }}Copy the code

We can further simplify the solution to implement a two-way distribution. Note that each enum instance has a fixed value (based on the order in which it is declared) and can be obtained using the ordinal() method. So we can use a two-dimensional array to map competitors to competing outcomes. Taking this approach leads to the simplest and most straightforward solution

package enums;
import static enums.Outcome.*;
enum RoShamBo6 implements Competitor<RoShamBo6> {
    PAPER, SCISSORS, ROCK;
    private static Outcome[][] table = {
            { DRAW, LOSE, WIN }, // PAPER
            { WIN, DRAW, LOSE }, // SCISSORS
            { LOSE, WIN, DRAW }, // ROCK
    };
    @Override
    public Outcome compete(RoShamBo6 other) {
        return table[this.ordinal()][other.ordinal()];
    }
    public static void main(String[] args) {
        RoShamBo.play(RoShamBo6.class, 20); }}Copy the code