第6章 枚举和注解

第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标记注解类型,就要花点时间考虑清楚,它是否真的应该为注解类型,想想标记接口是否会更加合适呢?


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值