首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》第6章 枚举和注解

关灯直达底部

日光之下,并无新事。

——《圣经》

枚举和注解都是在Java 1.5中引入的,虽然它们是后起之秀,但其功效不可小觑,枚举改变了常量的声明方式,注解耦合了数据和代码。本章就如何更好地使用注解和枚举提出了多条建议,以便读者能够在系统开发中更好地使用它们。

建议83:推荐使用枚举定义常量

常量声明是每一个项目都不可或缺的,在Java 1.5之前,我们只有两种方式的声明:类常量和接口常量,若在项目中使用的是Java 1.5之前的版本基本上都是如此定义的。不过,在1.5版以后有了改进,即新增了一种常量声明方式:枚举声明常量,看如下代码:


enum Season{

Spring, Summer, Autumn, Winter;

}


这是一个简单的枚举常量命名,清晰又简单。顺便提一句,JLS(Java Language Specification, Java语言规范)提倡枚举项全部大写,字母之间用下划线分隔,这也是从常量的角度考虑的(当然,使用类似类名的命名方式也是比较友好的)。

可能有读者要问了:枚举常量与我们经常使用的类常量和静态常量相比有什么优势?问得好,枚举的优点主要表现在以下四个方面。

(1)枚举常量更简单

简不简单,我们来对比一下两者的定义和使用情况就知道了。先把Season枚举翻写成接口常量,代码如下:


interface Season{

int Spring=0;

int Summer=1;

int Autumn=2;

int Winter=3;

}


此处定义了春夏秋冬四个季节,类型都是int,这与Season枚举的排序值是相同的。首先对比一下两者的定义,枚举常量只需要定义每个枚举项,不需要定义枚举值,而接口常量(或类常量)则必须定义值,否则编译通不过,即使我们不需要关注其值是多少也必须定义;其次,虽然两者被引用的方式相同(都是“类名.属性”,如Season.Spring),但是枚举表示的是一个枚举项,字面含义是春天,而接口常量却是一个int类型,虽然其字面含义也是春天,但在运算中我们势必要关注其int值。

(2)枚举常量属于稳态型

例如,我们要给外星人描述一下地球上的春夏秋冬是什么样子的,使用接口常量应该是这样写。


public void describe(int s){

//s变量不能超越边界,校验条件

if(s>=0&&s<4){

switch(s){

case Season.Summer:

System.out.println(/"Summer is very hot!/");

break;

case Season.Winter:

System.out.println(/"Winter is very cold!/");

break;

……

}

}

}


很简单,先使用switch语句判断是哪一个常量,然后输出。但问题是我们得对输入值进行检查,确定是否越界,如果常量非常庞大,校验输入就成了一件非常麻烦的事情,但这是一个不可逃避的过程,特别是如果我们的校验条件不严格,虽然编译照样可以通过,但是运行期就会产生无法预知的后果。

我们再来看看枚举常量是否能够避免校验问题,代码如下:


public void describe(Season s){

switch(s){

case Summer:

System.out.println(Season.Summer+/"is very hot/");

break;

case Winter:

System.out.println(Season.Winter+/"is very cold/");

break;

……

}

}


不用校验,已经限定了是Season枚举,所以只能是Season类的四个实例,即春夏秋冬4个枚举项,想输入一个int类型或其他类型?门都没有!这也是我们最看重枚举的地方:在编译期间限定类型,不允许发生越界的情况。

(3)枚举具有内置方法

有一个很简单的问题:如果要列出所有的季节常量,如何实现呢?接口常量或类常量可以通过反射来实现,这没错,只是虽然能实现,但会非常繁琐,读者有兴趣可以自己写一个反射类实现此功能(当然,一个一个地手动打印输出常量,也可以算是列出)。对于此类问题,使用枚举就可以非常简单地解决,代码如下:


public static void main(Stringargs){

for(Season s:Season.values()){

System.out.println(s);

}

}


通过values方法获得所有的枚举项,然后打印出来即可。如此简单,得益于枚举内置的方法,每个枚举都是java.lang.Enum的子类,该基类提供了诸如获得排序值的ordinal方法、compareTo比较方法等,大大简化了常量的访问。

(4)枚举可以自定义方法

这一点似乎并不是枚举的优点,类常量也可以有自己的方法呀,但关键是枚举常量不仅可以定义静态方法,还可以定义非静态方法,而且还能够从根本上杜绝常量类被实例化。比如我们要在常量定义中获得最舒服季节的方法,使用常量枚举的代码如下所示:


enum Season{

Spring, Summer, Autumn, Winter;

//最舒服的季节

public static Season getComfortableSeason(){

return Spring;

}

}


我们知道每个枚举项都是该枚举的一个实例,对于我们的例子来说,也就表示Spring其实是Season的一个实例,Summer也是其中一个实例,那我们在枚举中定义的静态方法既可以在类(也就是枚举Season)中引用,也可以在实例(也就是枚举项Spring、Summer、Autumn、Winter)中引用,看如下代码:


public static void main(Stringargs){

System.out.println(/"The most comfortable season is/"+Season.getComfortableSeason());

}


那如果使用类常量要如何实现呢?代码如下:


class Season{

public final static int Spring=0;

public final static int Summer=1;

public final static int Autumn=2;

public final static int Winter=3;

//最舒服的季节

public static int getComfortableSeason(){

return Spring;

}

}


想想看,我们要怎么才能打印出/"The most comfortable season is Spring/"这句话呢?除了使用switch判断外没有其他办法了。

虽然枚举常量在很多方面比接口常量和类常量好用,但是有一点它是比不上接口常量和类常量的,那就是继承,枚举类型是不能有继承的,也就是说一个枚举常量定义完毕后,除非修改重构,否则无法做扩展,而接口常量和类常量则可以通过继承进行扩展。但是,一般常量在项目构建时就定义完毕了,很少会出现必须通过扩展才能实现业务逻辑的场景。

注意 在项目开发中,推荐使用枚举常量代替接口常量或类常量。