第30条:用enum代替int常量
枚举类型(enum type)是指由一组固定的常量组成合法值的类型。
Java枚举类型背后的基本想法非常简单:它们就是通过公有的静态final域为每个枚举常量导出实例的类。因为没有可以访问的构造器,枚举类型是真正
的final。因为客户端既不能创建枚举类型的实例,也不能对它进行扩展,因此很可能没有实例,而只有声明过的枚举常量。换句话说,枚举类型是实例
受控的。它们是单例的泛型化,本质上是单元素的枚举。
一般来说,枚举会优先使用comparable而非int常量。与int常量相比,枚举有个小小的性能缺点,即装载和初始化枚举时会有空间和时间的成本。除了
受资源约束的设备,例如手机和烤面包机之外,在实践中不必太在意这个问题。
那么什么时候应该使用枚举呢?每当需要一组固定常量的时候。当然,这包括“天然的枚举类型”,例如行星、一周的天数以及棋子的数目等。但它也包括
在编译时就知道其所有可能值的其他集合,例如菜单的选项、操作代码以及命令行标记等。枚举类型中的常量集并不一定要始终保持不变。专门设计枚举
特性是考虑到枚举类型的二进制兼容演变。
总而言之,与int常量相比,枚举类型的优势是不言而喻的。枚举要易读得多,也更加安全,功能更加强大。许多枚举都不需要显示的构造器或者成员,但
许多其他枚举则受益于“每个常量与属性的关联”以及“提供行为受这个属性影响的方法”。只有极少数的枚举受益于将多种行为与单个方法关联。在这种相对
少见的情况下,特定于常量的方法要优先于启用自有值的枚举。如果多个枚举常量同时共享相同的行为,则考虑策略相关。
第31条:用实例域代替序数
永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中。
第32条:用EnumSet代替位域
位域表示法也允许利用位操作,有效地执行像union(联合)和intersection(交集)这样的集合操作。但位域有着int枚举常量的所有缺点,甚至更多。当位域以数字
形式打印时,翻译位域比翻译简单的int枚举常量要困难得多。甚至,要遍历位域表示的所有元素也没有很容易的方法。
总而言之,正是因为枚举类型要用在集合中,所以没有理由用位域来表示它。EnumSet类集位域的简洁和性能优势及第30条中所述的枚举类型的所有优点于
一身。实际上EnumSet有个缺点,即截止Java 1.6发行版本,它都无法创建不可变的EnumSet,但是这一点很可能在即将出来的版本中得到修正。同时,可以用
Collections.unmodifiableSet将EnumSet封装起来,但是简洁性和性能会受到影响。
第33条:用EnumMap代替序数索引
最好不要用序数来索引数组,而要使用EnumSetMap。如果你所表示的这种关系是多维的,就使用EnumMap<...,EnumMap<...>>。应用程序的程序员在一般情况
下都不使用Enum.ordinal,即使要用也很少,因此这是一种特殊情况。
第34条:用接口模拟可伸缩的枚举
对于可伸缩的枚举类型而言,至少有一种具有说服力的用例,这就是操作码(operation code),也称作opcode。操作码是指这样的枚举类型:它的元素表示在
某种机器上的那些操作,例如第30条中的Operation类型,它表示一个简单的计算器中的某些函数。有时候,要尽可能地让API的用户提供它们自己的操作,
这样可以有效地扩展API所提供的操作集。
幸运的是,有一种很好的方法可以利用枚举类型来实现这种效果。由于枚举类型可以通过给操作码类型和(属于接口的标准实现的)枚举定义接口,来实现任意接口,
基本的想法就是利用这一事实。
第二种方法是使用Collection<? Extends Operation>,这是个有限制的通配符类型(bounded wildcard type),作为opSet参数的类型。
总而言之,虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟。这样允许客户端编写自己的枚举实现接口。
如果API是根据接口编写的,那么在可以使用基础枚举类型的任何地方,也都可以使用这些枚举。
第35条:注解优先于命名模式
命名模式的缺点:首先,文字拼写错误会导致失败,且没有任何提示。第二个缺点,无法确保它们只用于相应的程序元素上。第三个缺点,它们没有提供将参数
值与程序元素关联起来的好方法。
除了“工具铁匠”之外,大多数程序员都不必定义注解类型。但是所有的程序员都应该使用Java平台所提供的预定义的注解类型。还要考虑使用IDE或者静态分析
工具所提供的任何注解。这种注解可以提升由这些工具所提供的诊断信息的质量。但是要注意这些注解还没有标准化,因此如果变换工具或者形成标准,就有很多
工作要做了。
第36条:坚持使用Override注解
如果在你想要的每个方法声明中使用Override注解来覆盖超类声明,编译器就可以替你防止大量的错误,但又一个例外。在具体的类中,不必标注你确信覆盖了
抽象方法声明的方法(虽然这么做也没有什么坏处)。
第37条:用标记接口定义类型
标记接口(marker interface)是没有包含方法声明的接口,而只是指明(或者“标明”)一个类实现了具有某种属性的接口。
标记接口有两点胜过标记注解。首先,也是最重要的一点是,标记接口定义的类型是由被标记类的实例实现的;标记注解则没有定义这样的类型。这个类运行你在
编译时捕捉在使用标记注解的情况下要到运行时才能捕捉到的错误。
标记接口胜过标记注解的另一个优点是,它们可以被更加精确地进行锁定。
标记注解胜过标记接口的最大优点在于,它可以通过默认的方式添加一个或者多个注解类型元素,给已被使用的注解类型添加更多的信息。
标记注解的另一个优点在于,它们是更大的注解机制的一部分。
标记接口和标记注解都各有用处。如果想要定义一个任何新方法都不会与之关联的类型,标记接口就是最好的选择。如果想要标记程序元素而非类和接口,考虑到
未来可能要给标记添加更多的信息,或者标记要适合于已经广泛使用了注解类型的框架,那么标记注解就是正确的选择。如果你发现自己在编写的是目标为
ElementType.TYPE标记注解类型,就要花点时间考虑清楚,它是否真的应该为注解类型,想想标记接口是否会更加合适呢?