effective java 第三周

第6章 枚举和注解

第30条:用 enum 代替 int 常量

在没有 enum 之前表示枚举类型的常用模式时声明一组具名的 int 常量或 String 常量,这种方式有非常多不足,在类型安全性和使用方便性方面没有帮助。

Java 1.5 版本提供了 enum 枚举,其基本想法是:通过公有的静态 final 域为每个枚举常量导出实例的类,不提供可访问的构造器,不能扩展,所以枚举类型是实例受控的。

枚举提供了编译时的类型安全,如果某个参数指定就是枚举类型,那么传递到这里就一定是枚举值之中的一个。

枚举类型还允许添加任意的域和方法(这里可以将它看成就是一个普通的类),数据,行为和常量关联起来。

在枚举中有一种方法可以将不同的行为与每个枚举常量关联起来,而不是去根据类型判断执行不同的行为:在枚举类型中声明一个抽象的 apply 方法,并在特定于常量的类主题中覆盖它。这种方法被称为特定于常量的方法实现。

代码如下:

public enum Operation{
  PLUS (double apply(double x, double y){return x + y;}),
  MINUS {double apply(double x, double y){return x - y;}},
  TIMES {double apply(double x, double y){return x * y;}},
  DIVIDE {double apply(double x, double y){return x / y;}};
  
  abstract double apply(double x, double y);
}

与 int 常量相比,枚举有一个小小的性能缺点,即装载和初始化枚举时会有空间和时间的成本。所以在早期的 Android 版本中并不推荐使用枚举,过于占用内存,后来有所改善,可以适当地使用。

第31条:用实例域代替序数

许多枚举天生就合一个单独的 int 值相关联,而所有枚举都有一个 ordinal 方法,它返回每一个枚举常量在类型中的数字位置,这个就是序数。

不建议使用序数的原因是:不好维护,一旦重新排序了,之前依靠 oridinal 数字的功能就会被破坏;也无法单独给某个 int 值添加常量。

所以,推荐的做法是永远不要根据枚举的序数导出与它相关联的值,而是要将它保存在一个实例域中:

public enum Ensemble {
  SOLO(1), DUET(2),TRIO(3),
  QUARTET(4),QUINTET(5);
  
  private final int numberOfMusicians;
  
  Ensemble(int size) {this.numberOfMusicians = size;}
  
  public int numberOfMusicians(){return numberOfMusicians;}
}

第32条:用 EnumSet 代替位域

如果一个枚举类型的元素主要用在集合中,一般就使用 int 枚举模式,将2的不同倍数赋予每个常量

public class Text{
    public static final int STYLE_BOLD = 1;  // 1
    public static final int STYLE_ITALIC = 1 << 1;  // 2
    public static final int STYLE_UNDERLINE = 1 << 2;   // 4
    public static final int STYLE_STRIKETHROUCH = 1 << 3; // 8
}

这个就是位域,可以用 OR 位运算将几个常量合并到一个集合中,也可以使用位操作高效地执行像联合和交集这样的集合操作。但是位域有着 int 枚举常量的所有缺点,甚至更多,打印的时候难以阅读,遍历位域表示的所有元素也不容易。

EnumSet 类有效地表示从单个枚举类型中提取的多个值的多个集合。在其内部实现上,每个 EnumSet 内容都表示为位矢量,如果底层的枚举类型有64或更少的元素,整个 EnumSet 用单个 long 表示,如果多于64,则用 long[] 表示。因此它的性能比得上位域的性能。

public class Text{
  public enum Style {BOLD, ITALIC, UNDERLINE, STRIKETHROUCH}
  
  // any Set could be passed in, but EnumSet is clearly best
  public void applyStyles(Set