Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必不少人都读过,号称Java四大名著之一,不过第二版2009年出版,到如今已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深入的变化。
在这里第一时间翻译成中文版。供你们学习分享之用。java
在几乎全部方面,枚举类型都优于本书初版中描述的类型安全模式[Bloch01]。 从表面上看,一个例外涉及可扩展性,这在原始模式下是可能的,但不受语言结构支持。 换句话说,使用该模式,有可能使一个枚举类型扩展为另外一个; 使用语言功能特性,它不能这样作。 这不是偶然的。 大多数状况下,枚举的可扩展性是一个糟糕的主意。 使人困惑的是,扩展类型的元素是基类型的实例,反之亦然。 枚举基本类型及其扩展的全部元素没有好的方法。 最后,可扩展性会使设计和实现的不少方面复杂化。安全
也就是说,对于可扩展枚举类型至少有一个有说服力的用例,这就是操做码( operation codes),也称为opcodes。 操做码是枚举类型,其元素表示某些机器上的操做,例如条目 34中的Operation
类型,它表示简单计算器上的功能。 有时须要让API的用户提供他们本身的操做,从而有效地扩展API提供的操做集。app
幸运的是,使用枚举类型有一个很好的方法来实现这种效果。基本思想是利用枚举类型能够经过为opcode类型定义一个接口,并实现任意接口。例如,这里是来自条目 34的Operation
类型的可扩展版本:ide
// Emulated extensible enum using an interface public 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; } }
虽然枚举类型(BasicOperation
)不可扩展,但接口类型(Operation
)是能够扩展的,而且它是用于表示API中的操做的接口类型。 你能够定义另外一个实现此接口的枚举类型,并使用此新类型的实例来代替基本类型。 例如,假设想要定义前面所示的操做类型的扩展,包括指数运算和余数运算。 你所要作的就是编写一个实现Operation
接口的枚举类型:学习
// Emulated extension enum public 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; } }
只要API编写为接口类型(Operation
),而不是实现(BasicOperation
),如今就能够在任何可使用基本操做的地方使用新操做。请注意,没必要在枚举中声明apply
抽象方法,就像您在具备实例特定方法实现的非扩展枚举中所作的那样(第162页)。 这是由于抽象方法(apply
)是接口(Operation
)的成员。测试
不只能够在任何须要“基本枚举”的地方传递“扩展枚举”的单个实例,并且还能够传入整个扩展枚举类型,并使用其元素。 例如,这里是第163页上的一个测试程序版本,它执行以前定义的全部扩展操做:this
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)); }
注意,扩展的操做类型的类字面文字(ExtendedOperation.class
)从main
方法里传递给了test
方法,用来描述扩展操做的集合。这个类的字面文字用做限定的类型令牌(条目 33)。opEnumType
参数中复杂的声明(<T extends Enum<T> & Operation> Class<T>
)确保了Class对象既是枚举又是Operation
的子类,这正是遍历元素和执行每一个元素相关联的操做时所须要的。命令行
第二种方式是传递一个Collection<? extends Operation>
,这是一个限定通配符类型(条目 31),而不是传递了一个class对象:翻译
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)); }
生成的代码稍微不那么复杂,tes
t方法灵活一点:它容许调用者将多个实现类型的操做组合在一块儿。另外一方面,也放弃了在指定操做上使用EnumSe
t(条目 36)和EnumMap
(条目 37)的能力。设计
上面的两个程序在运行命令行输入参数4和2时生成如下输出:
4.000000 ^ 2.000000 = 16.000000 4.000000 % 2.000000 = 0.000000
使用接口来模拟可扩展枚举的一个小缺点是,实现不能从一个枚举类型继承到另外一个枚举类型。若是实现代码不依赖于任何状态,则可使用默认实现(条目 20)将其放置在接口中。在咱们的Operation
示例中,存储和检索与操做关联的符号的逻辑必须在BasicOperation
和ExtendedOperation
中重复。在这种状况下,这并不重要,由于不多的代码是冗余的。若是有更多的共享功能,能够将其封装在辅助类或静态辅助方法中,以消除代码冗余。
该条目中描述的模式在Java类库中有所使用。例如,java.nio.file.LinkOption
枚举类型实现了CopyOption
和OpenOption
接口。
总之,虽然不能编写可扩展的枚举类型,可是你能够编写一个接口来配合实现接口的基本的枚举类型,来对它进行模拟。这容许客户端编写本身的枚举(或其它类型)来实现接口。若是API是根据接口编写的,那么在任何使用基本枚举类型实例的地方,均可以使用这些枚举类型实例。