《Effective Java》-枚举和注解

110 阅读5分钟

枚举和注解

Item 34: Use enums instead of int constants

使用enum代替int常量

枚举类型指的是一组固定常量组成的合法值的类型。

使用int枚举模式的程序十分脆弱,这是因为int枚举模式是编译时常量,当其值发生改变的时候,客户端必须重新编译,否则其值不准确。此外,int类型枚举模式在输出是只是一个数字,可读性十分差。

使用枚举类可以有效解决上述问题。Java中的枚举类本质上也是int。并且,枚举类是单例的,实例受控的,本质上是单元素的枚举。

枚举类型可以添加任意的类型的方法和字段。为了将数据与枚举常量关联起来,需要声明实例字段,并编写一个带有数据,并将数组保存在字段中的构造器。

如果一个枚举类具有普遍适用性,其就应该称为一个顶层类;如果其只是被用在特定的顶层中,则其应该称为该顶层类中的一个成员类。

Item 35: Use instance fields instead of ordinals

使用实例字段替代序数

枚举类天生就是和int值相关联的,所有的枚举类都有一个ordinal()方法,该方法返回每个枚举值在类型中的数字位置。

但是,永远不要根据枚举类的序数导出与其相关联的值,而是要将其保存到一个实例字段中

Item 36: Use EnumSet instead of bit fields

使用EnumSet替代位域

使用or位运算符将几个常量合并到一个集合中,称为位域(bit field)。

text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

可以利用位域的特点,通过位运算,有效地实现交集、并集等集合操作。

然而,这样的位域具有int枚举模式所有的缺点:

  • 易读性比较差
  • 需要预先测定位长,使用int类型或者long,一旦确定,就不能超出其位宽度

使用EnumSet可以有效地表示从单个枚举类型中提取的多个值的集合。具体的实现细节中,EnumSet使用单个long来表示,性能和位域相当。


// EnumSet - a modern replacement for bit fields 
public class Text { 
    public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH } 
    // Any Set could be passed in, but EnumSet is clearly best 
    public void applyStyles(Set<Style> styles) { ... } 
}
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));

Item 37: Use EnumMap instead of ordinal indexing

使用EnumMap替代序数索引

需要重新理解。

Item 38: Emulate extensible enums with interfaces

用接口模拟可扩展的枚举类

虽然枚举类型是不可以扩展的,但是接口类型可以扩展的。可以定义一个枚举类型,他实现某个接口,并使用这个新类型的实例替代基本类型。

使用接口模拟可伸缩枚举有一个小小的不足,就是无法将接口的实现从一个枚举类继承到另一个枚举类。但是,如果接口中的API的实现不依赖于任何状态,那么可以直接在接口中定义缺省实现。

Item 39: Prefer annotations to naming patterns

注解优先于命名模式

一般情况下,使用命名模式来表明某个组件需要通过某种框架或者工具进行特殊处理。但是这种模式有几个比较明显的缺点:

  • 直接的拼写错误会导致程序运行失败
  • 无法确保只用于相应的程序元素上
  • 不提供将参数值和程序元素相关联的好方法

使用注解可以有效地避免这些问题。

元注解:用于定义其他注解的注解

  • @Target:注解应用的目标,类、方法、字段
  • @Retention:注解的保留策略,源码、编译时、运行时
  • @Repeatable:是否可重复应用于同一个元素
  • @Inherited:是否具有继承性

注解永远不会改变被注解代码的语义,但是可使用工具或者框架对其进行特殊处理。

除了平台框架程序员之外,大多数程序员都不必须定义注解类型,但是所有的程序员都应该使用Java平台所提供的预定义的注解类型,以及IDE或者静态分析工具提供的任何注解。

Item 40: Consistently use the Override annotation

坚持使用@Override注解

@Override注解只能用于方法上,用于表明被注解的方法覆盖了父类的某个方法。

应该在想要覆盖超类声明的每个方法的声明中使用@Override注解,编译器可以替你防止大量的错误。

Item 41: Use marker interfaces to define types

使用标记接口定义类型

标记接口是不包含方法声明的接口,只是标记某个类实现了具有某种属性的接口,例如@RandomAccess@Serializable

标记接口定义的类型是由被标记类的实例实现的,标记注解则没有定义这样的类型。标记注解的存在,可以将运行时才能检测出来的异常提前到编译时。

标记接口和之前的标记注解有什么区别呢?

  • 标记接口: 适用于需要在类型系统中明确标识某种特性的场景。
    • 例如,Serializable 接口用于标识类可以被序列化,Cloneable 接口用于标识类可以被克隆。
  • 标记注解: 适用于需要在代码中添加元数据,但不影响类型系统的场景。
    • 例如,@Deprecated 用于标记过时的代码,@FunctionalInterface 用于标记函数式接口。