Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。
在这里第一时间翻译成中文版。供大家学习分享之用。
Java支持两种引用类型的特殊用途的系列:一种称为枚举类型的类和一种称为注解类型的接口。 本章讨论使用这些类型系列的最佳实践。
34. 使用枚举类型替代整型常量枚举是其合法值由一组固定的常量组成的一种类型,例如一年中的季节,太阳系中的行星或一副扑克牌中的套装。 在将枚举类型添加到该语言之前,表示枚举类型的常见模式是声明一组名为int的常量,每个类型的成员都有一个常量:
// The int enum pattern - severely deficient! public static final int APPLE_FUJI = 0; public static final int APPLE_PIPPIN = 1; public static final int APPLE_GRANNY_SMITH = 2; public static final int ORANGE_NAVEL = 0; public static final int ORANGE_TEMPLE = 1; public static final int ORANGE_BLOOD = 2;这种被称为int枚举模式的技术有许多缺点。 它没有提供类型安全的方式,也没有提供任何表达力。 如果你将一个Apple传递给一个需要Orange的方法,那么编译器不会出现警告,还会用==运算符比较Apple与Orange,或者更糟糕的是:
// Tasty citrus flavored applesauce! int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN;请注意,每个Apple常量的名称前缀为APPLE_,每个Orange常量的名称前缀为ORANGE_。 这是因为Java不为int枚举组提供名称空间。 当两个int枚举组具有相同的命名常量时,前缀可以防止名称冲突,例如在ELEMENT_MERCURY和PLANET_MERCURY之间。
使用int枚举的程序很脆弱。 因为int枚举是编译时常量[JLS,4.12.4],所以它们的int值被编译到使用它们的客户端中[JLS,13.1]。 如果与int枚举关联的值发生更改,则必须重新编译其客户端。 如果没有,客户仍然会运行,但他们的行为将是不正确的。
没有简单的方法将int枚举常量转换为可打印的字符串。 如果你打印这样一个常量或者从调试器中显示出来,你看到的只是一个数字,这不是很有用。 没有可靠的方法来迭代组中的所有int枚举常量,甚至无法获得int枚举组的大小。
你可能会遇到这种模式的变体,其中使用了字符串常量来代替int常量。 这种称为字符串枚举模式的变体更不理想。 尽管它为常量提供了可打印的字符串,但它可以导致初级用户将字符串常量硬编码为客户端代码,而不是使用属性名称。 如果这种硬编码的字符串常量包含书写错误,它将在编译时逃脱检测并导致运行时出现错误。 此外,它可能会导致性能问题,因为它依赖于字符串比较。
幸运的是,Java提供了一种避免int和String枚举模式的所有缺点的替代方法,并提供了许多额外的好处。 它是枚举类型[JLS,8.9]。 以下是它最简单的形式:
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH } public enum Orange { NAVEL, TEMPLE, BLOOD }从表面上看,这些枚举类型可能看起来与其他语言类似,比如C,C ++和C#,但事实并非如此。 Java的枚举类型是完整的类,比其他语言中的其他语言更强大,其枚举本质本上是int值。
Java枚举类型背后的基本思想很简单:它们是通过公共静态final属性为每个枚举常量导出一个实例的类。 由于没有可访问的构造方法,枚举类型实际上是final的。 由于客户既不能创建枚举类型的实例也不能继承它,除了声明的枚举常量外,不能有任何实例。 换句话说,枚举类型是实例控制的(第6页)。 它们是单例(条目 3)的泛型化,基本上是单元素的枚举。
枚举提供了编译时类型的安全性。 如果声明一个参数为Apple类型,则可以保证传递给该参数的任何非空对象引用是三个有效Apple值中的一个。 尝试传递错误类型的值将导致编译时错误,因为会尝试将一个枚举类型的表达式分配给另一个类型的变量,或者使用==运算符来比较不同枚举类型的值。
具有相同名称常量的枚举类型可以和平共存,因为每种类型都有其自己的名称空间。 可以在枚举类型中添加或重新排序常量,而无需重新编译其客户端,因为导出常量的属性在枚举类型与其客户端之间提供了一层隔离:常量值不会编译到客户端,因为它们位于int枚举模式中。 最后,可以通过调用其toString方法将枚举转换为可打印的字符串。