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