Tips
"Effective Java, third Edition" an English version has been published, the second edition of this book presumably many people have read, known as one of the four major Java books, but the second edition of 2009 published, to now nearly 8 years, but with Java 6, 7, 8, and even 9 of the release, the Java language has undergone profound changes.
In the first time here translated into Chinese version. For everyone to learn to share.
38. Simulating an extensible enumeration using an interface
In almost all respects, enumeration types are better than the type safety model described in the first edition of this book [Bloch01]. On the surface, an exception involves extensibility, which is possible in the original mode, but is not supported by the language structure. In other words, with this pattern, it is possible to extend one enumeration type to another; It cannot do this by using the language features feature. It's not accidental. In most cases, the extensibility of enumerations is a bad idea. It is confusing that an element of an extension type is an instance of a base type, and vice versa. There is no good way to enumerate the base types and all of their extended elements. Finally, scalability complicates many aspects of design and implementation.
That is, there is at least one compelling use case for an extensible enumeration type, which is the opcode (operation codes), also known as opcodes. An opcode is an enumeration type whose elements represent operations on some machines, such as the type in entry 34 Operation
, which represents functionality on a simple calculator. Sometimes it is necessary for the users of the API to provide their own operations, effectively extending the set of operations provided by the API.
Fortunately, there is a good way to do this with enum types. The basic idea is to use an enumeration type to define an interface for the opcode type and implement any interface. For example, here is an extensible version of the type from entry 34 Operation
:
// Emulated extensible enum using an interfacepublic interface Operation { double apply(double x, double y);}public enum BasicOperation implements Operation { PLUS("+") { public double apply(double x, double y) { return x + y; } }, MINUS("-") { public double apply(double x, double y) { return x - y; } }, TIMES("*") { public double apply(double x, double y) { return x * y; } }, DIVIDE("/") { public double apply(double x, double y) { return x / y; } }; private final String symbol; BasicOperation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; }}
Although the enumeration type ( BasicOperation
) is not extensible, the interface type ( Operation
) is extensible, and it is the type of interface used to represent operations in the API. You can define another enumeration type that implements this interface and use an instance of this new type instead of the base type. For example, suppose you want to define an extension of the type of operation shown earlier, including exponential and remainder operations. All you have to do is write an Operation
enumeration type that implements the interface:
// Emulated extension enumpublic enum ExtendedOperation implements Operation { EXP("^") { public double apply(double x, double y) { return Math.pow(x, y); } }, REMAINDER("%") { public double apply(double x, double y) { return x % y; } }; private final String symbol; ExtendedOperation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; }}
As long as the API is written as an interface type ( Operation
) rather than implementation ( BasicOperation
), you can now use the new operation anywhere you can use the basic operation. Note that you do not have to declare an abstract method in an enumeration apply
, as you did in a non-extensible enumeration with instance-specific method implementations (page 162th). This is because the abstract method ( apply
) is a Operation
member of the interface ().
You can pass a single instance of an extended enumeration in any place where "basic enumeration" is needed, and you can pass in the entire extended enumeration type and use its elements. For example, here is a version of the test program on page 163th that performs all the extended operations previously defined:
public static void main(String[] args) { double x = Double.parseDouble(args[0]); double y = Double.parseDouble(args[1]); test(ExtendedOperation.class, x, y);}private static <T extends Enum<T> & Operation> void test( Class<T> opEnumType, double x, double y) { for (Operation op : opEnumType.getEnumConstants()) System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));}
Note that the class literal literal () of the extended operation type is ExtendedOperation.class
passed from the method to the main
test
method that describes the collection of extended operations. The literal literal of this class is used as a qualified type token (entry 33). opEnumType
complex declarations in parameters ( <T extends Enum<T> & Operation> Class<T>
) Ensure that the class object is both an enumeration and Operation
a subclass, which is what is needed to traverse the elements and perform the operations associated with each element.
The second way is to pass one Collection<? extends Operation>
, which is a qualified wildcard type (entry 31) instead of passing a class object:
public static void main(String[] args) { double x = Double.parseDouble(args[0]); double y = Double.parseDouble(args[1]); test(Arrays.asList(ExtendedOperation.values()), x, y);}private static void test(Collection<? extends Operation> opSet, double x, double y) { for (Operation op : opSet) System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));}
The generated code is slightly less complex, and the tes
T method is flexible: It allows the caller to group operations of multiple implementation types together. On the other hand, the ability to use EnumSe
T (entry 36) and EnumMap
(entry 37) on a specified operation is also discarded.
The above two programs produce the following output at Run command line input parameters 4 and 2 o'clock:
4.000000 ^ 2.000000 = 16.0000004.000000 % 2.000000 = 0.000000
One small disadvantage of using interfaces to emulate extensible enumerations is that implementations cannot inherit from one enumeration type to another. If the implementation code does not depend on any state, you can use the default implementation (entry 20) to place it in the interface. In our Operation
example, the logic for storing and retrieving the symbols associated with the operation must be BasicOperation
repeated in and ExtendedOperation
. In this case, this is not important, because very little code is redundant. If you have more sharing capabilities, you can encapsulate them in a helper class or static helper method to eliminate code redundancy.
The schema described in this entry is used in the Java class Library. For example, java.nio.file.LinkOption
an enumeration type implements the CopyOption
and OpenOption
interface.
In summary, Although you cannot write an extensible enumeration type, you can write an interface to emulate the basic enumeration type that implements the interface . This allows the client to write its own enumeration (or other type) to implement the interface. If the API is written based on an interface, you can use these enumeration type instances wherever you use the base enumeration type instances.
Effective Java Third edition--38. Simulating an extensible enumeration using an interface