`
TonyLian
  • 浏览: 402181 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

【第21条】用类来代替enum结构

阅读更多

要先说明一下:本书写作于2001年,正值作者参与建设JDK1.4的时期。后来到了JDK1.5,Java又将抛弃了多年的enum枚举重拾了起来。所以本条是在没有enum的时候写的。

 

    JDK1.4及以前版本省略了enum。其实enum也是一种struct,我们当然是用class来代替之,但为什么又要单独作为一条来讲呢?是因为用类来替代枚举的时候,比较容易(或者说事实中绝大多数人都已经)犯一些错误。

 

    比如我们要一个有三种颜色的Color,有人会定义为

public class Color {
    public static final String RED = "RED";
    public static final String GREEN = "GREEN";
    public static final String BLUE = "BLUE";
}

 

 这是我们非常常见的常量定义方法,通过 Color.RED 来使用。但是,你有没有想过,如果一个“不太合格”的程序员很有可能会写出这样的代码:

if (dotA.color.equals("RED")) {
    ......
}

 

这就将硬编码写到了程序中,而且如果不做Source Review是很难发现的。

 

这种写法还有一个“变种”,用int而非String:

 

public class Color {
    public static final int RED = 0;
    public static final int GREEN = 1;
    public static final int BLUE = 2;
}

 

当然,那个人可以继续写出 if (dotA.color == 0) { 这样的语句,Source Review 发现的概率更低了。

但是使用int型一个小“变种”,回来带一些好处:

public class Color {
    public static final int RED = 1;
    public static final int GREEN = 2;
    public static final int BLUE = 4;
}

 

每个值都是用2的整数幂,这样可以在需要保存多于一个状态的和的时候,将所有项目相加(或者位或)得到“保存值”。使用的时候,在通过此值与相应项目的位与后的Boolean值来判断是否包含之:

// 保存时
int value = Color.RED | Color.BLUE;   // 或 Color.RED + Color.BLUE
saveValue(value);   // 保存,可能是写入数据库

// 使用时
int value = getValue();   // 从数据库中取出
if (value & Color.RED) {
    system.out.println("包含红色");
} else if (value & Color.GREEN) {
    system.out.println("包含绿色");
} else if (value & Color.BLUE) {
    system.out.println("包含蓝色");
} 

// 结果会是:
// 包含红色
// 包含蓝色

 

当项目中这样的常量组越来越多,constants包中类会越来越多,甚至都想 import xxxx.constants.*; 了。于是有人开始把这些所有的常量组,放入一个Constants类中

public class Constants{
    // 颜色
   public static final int COLOR_RED = 1;
    public static final int COLOR_GREEN = 2;
    public static final int COLOR_BLUE = 4;

    // 形状
   public static final int SHAPE_CIRCLE = 1;
    public static final int SHAPE_RECTANGLE = 2;
    public static final int SHAPE_TRIANGLE = 4;
    ......
}
   

 现在import一个类就好了,但是,那个人又来了,这次他吸取了之前的教训,没有硬编码了,但是他写了:

if (dotA.color == Constants.SHAPE_CIRCLE) {  // A点的颜色是圆形吗?  &^!#&^*`~  倒!
   .......

这样的语句是不会被编译器挑出来的,那么你能做的,除了祈祷就是痛哭了!在之前的模式下,没有写成 if (dotA.color == Shape.CIRCLE) 就已经不错了。

 

    面对这个问题,书中给出了一个“尚未被人知晓”的方法——类型安全枚举模式。注意,它只是一种模式,再次强调JDK1.5之前并没有枚举,它实质上还是一个类。

 

public class Color {
    private final String name;

    private Color(String name){  // 使用者无法创建这个类的实例
        this.name = name;
    }

    public String toString() {
        return this.name;
    }

    public static final Color RED = new Color("red");
    public static final Color GREEN = new Color("green");
    public static final Color BLUE = new Color("blue");
}

 

私有的构造函数,保证了使用者无法创建这个类的实例,除了通过公有的静态final域导出的Color对象外,永远也不会再有其他实例存在。

 

    所谓“类型安全”正式它提供了编译时的类型安全性。任何传入的非null的对象引用,一定表示了三种颜色中的一种。这样的模式下,即防止了类型错误,有防止了硬编码问题。

if (dotA.color.equals(Color.RED)) {  // 前提:dotA的实现类中,color的类型既不是int也不是String,而是Color
    ......
}

 

    我们看到了两大好处。再看看JDK1.5提供的enum关键字(它对应一个类Enum),从使用方法和特征来说,类型安全枚举模式和JDK1.5的enum枚举是非常相似的。那么在没有枚举的年代,作者尚且建议我们使用类型安全枚举模式,如今有了enum,我们就更没有理由拒绝它了。

 

    但是,类型安全枚举模式和enum枚举就没有弱点吗?当然不是,首当其冲的就是刚才提到的,当常量组非常多的时候,使用N多的类型安全枚举模式或enum枚举,可能是一件让人头痛的事情。也许你会因为这一点而一票否决了它。

 

    再有就是如果对应枚举中项目的值(如前例中的dotA.color)将要保存到数据库或外部文件时,势必要用一个int或String来代表之,为此可能要在类型安全枚举类中增加一个private value,然后toString方法返回这个value的String形式;或者在enum中定义抽象方法(类似toString)。但这也可能导致toString方法的滥用,而失去“类型安全”的保护。

 

    最后,当类型安全枚举模式的类(没有研究enum枚举可否)实现了序列化后,如果期待不同版本程序序列化出来的字符流可以相互反序列化,那么后续版本就只能够在原来的最后面追加元素。(当然这一点对于int型和String型常量类也是一样的,而且还可能更糟)

 

    本来还想好好研究一下enum,但是,刚刚得知稍后要进行的公司羽毛球比赛被抽到的下下签,没有心情了,以后再说吧。今天的笔记就写到这儿了。

 

 

 

【Effective Java 学习笔记】系列连载专题请见:
http://tonylian.iteye.com/categories/64208

 

分享到:
评论

相关推荐

    数据结构答案(完整版)

    - 使用`std::swap()`函数来代替文中提到的自定义符号`<->`进行变量交换。 - 输入三个整数后,通过三次比较和可能的交换,确保它们按从大到小的顺序输出。 ##### 1.17 求k阶斐波那契序列的第m项的值 ```c Status ...

    C++ Primer中文版(第5版)李普曼 等著 pdf 1/3

     第Ⅲ部分 类设计者的工具 437  第13章 拷贝控制 439  13.1 拷贝、赋值与销毁 440  13.1.1 拷贝构造函数 440  13.1.2 拷贝赋值运算符 443  13.1.3 析构函数 444  13.1.4 三/五法则 447  13.1.5 使用=default...

    C++Primer(第5版 )中文版(美)李普曼等著.part2.rar

     第Ⅲ部分 类设计者的工具 437  第13章 拷贝控制 439  13.1 拷贝、赋值与销毁 440  13.1.1 拷贝构造函数 440  13.1.2 拷贝赋值运算符 443  13.1.3 析构函数 444  13.1.4 三/五法则 447  13.1.5 使用=default...

    java面试宝典 吐血推荐,很全面

    - **区别**: `&&` 具有短路特性,即如果第一个表达式为假,则第二个表达式不会被评估;而`&`会一直评估所有表达式,即使结果已知。 - **注意事项**: 在条件判断中优先使用`&&`以提高效率和安全性。 **4. 在JAVA中...

    java 面试宝典

    两者之间的主要区别在于 `&&` 具有短路效应:如果第一个表达式为 `false`,则第二个表达式不会被评估。而 `&` 操作符会始终计算两边的表达式。 **4. 在 JAVA 中如何跳出当前的多重嵌套循环?** 在 Java 中,可以...

    05计算机专业C++语言程序设计期中考试复习题.doc

    2. **布尔运算**:题目中的第二题涉及布尔逻辑运算符`&&`,表示逻辑与。当`x`和`y`均为`bool`类型时,`x && y`只有在两者都为真时才为真。正确答案是D,它们都为真。 3. **逻辑表达式取反**:第三题要求找出逻辑...

    大一大二mysql笔记

    2. 第二范式(2NF):非主键属性完全依赖于主键。 3. 第三范式(3NF):消除传递依赖。 七、数据库安全性 1. 用户权限管理:GRANT和REVOKE命令分配和撤销用户权限。 2. 视图(View):提供安全访问,只允许用户看到...

    提高C#编程水平的50个要点

    静态构造函数仅在第一次访问类的静态成员时执行一次。 ```csharp public class MyClass { public static string StaticValue; static MyClass() { StaticValue = "Hello, World!"; } } ``` #### 14. 用多个...

    亮剑.NET深入体验与实战精要2

    因pdf的容量过大分4个压缩包打包,还有一个源码另外下载。 《.NET深入体验与实战精要》作者身为从事.NET一线开发的资深开发专家,常年耕耘...15.5.14 使用视图代替跨库操作 572 15.5.15 尽量避免大事务操作 572 15.5.16...

    亮剑.NET深入体验与实战精要3

    因pdf的容量过大分4个压缩包打包,还有一个源码另外下载。 《.NET深入体验与实战精要》作者身为从事.NET一线开发的资深开发专家,常年耕耘...15.5.14 使用视图代替跨库操作 572 15.5.15 尽量避免大事务操作 572 15.5.16...

    (完整word)C语言判断练习.doc

    8. 定义二维数组时,第一维长度可以省略,宏定义是用宏名代替一字符串,也就是作简单的置换,不作语法检查。 9. 逻辑运算优先级别都低于算术运算,continue 语句的作用是提前结束整个循环的执行。 10. 打开文件的...

    C语言【清华大学出版社】西安交通大学课件第03章.pptx

    例如,十进制数10在计算机中存储为二进制`0000 0000 0000 0000 0000 0000 0000 1010`,而其补码表示为正数时不变,为负数时会取反加1,如-10的补码是`1111 1111 1111 1111 1111 1111 1111 0110`。 理解这些基础知识...

    你必须知道的495个C语言问题

    2.22 有没有一种自动方法来跟踪联合的哪个域在使用? 枚举 2.23 枚举和一组预处理的#define有什么不同? 2.24 枚举可移植吗? 2.25 有什么显示枚举值符号的容易方法吗? 位域 2.26 一些结构声明中的这些...

    C 语言编程常见问题解答.chm

    3 什么时候用一条switch语句比用多条if语句更好? 1. 4 switch语句必须包含default分支吗? 1. 5 switch语句的最后—个分支可以不要break语句吗? 1. 6 除了在for语句中之外,在哪些情况下还要使用逗号运算? 1. 7...

    C语言FAQ 常见问题列表

    o 4.10 如果我不使用表达式的值, 我应该用 ++i 或 i++ 来自增一个变量吗? o 4.11 为什么如下的代码 int a = 100, b = 100; long int c = a * b; 不能工作? o 4.12 我需要根据条件把一个复杂的表达式赋值给两个...

    doxygen配置说明[定义].pdf

    例如,可以设置一个别名`@todo`来代替`@see \todo`。 3. **ALLEXTERNALS**:如果开启,Doxygen会处理所有外部链接的文档,这对于构建完整的项目文档非常有用。 4. **ALPHABETICAL_INDEX**:开启后,Doxygen会生成...

    《你必须知道的495个C语言问题》

    2.22 有没有一种自动方法来跟踪联合的哪个域在使用? 30 枚举 31 2.23 枚举和一组预处理的#define有什么不同? 31 2.24 枚举可移植吗? 31 2.25 有什么显示枚举值符号的容易方法吗? 31 位域 31 2.26 ...

Global site tag (gtag.js) - Google Analytics