J2SE 5.0 (Tiger) 的发布是 Java 语言发展史上的一个重要的里程碑 , 是迄今为止在 Java 编程方面所取得的最大进步 .
J2SE 5.0 提供了很多令人激动的特性 . 这些特性包括范型 (generics) 的支持 , 枚举类型 (enumeration) 的支持 , 元数据 (metadata) 的支持 , 自动拆箱 (unboxing)/ 装箱 (autoboxing), 可变个数参数 (varargs), 静态导入 (static imports), 以及新的线程架构 (Thread framework).
随着 J2SE 5.0 的推出 , 越来越多的集成开发环境 (IDE) 支持 J2SE 5.0 的开发 . 著名的开源 Java IDE Eclipse 从 3.1M4 开始支持 J2SE 5.0 的开发 , 目前最新的版本是 3.1RC4.
本系列将介绍 J2SE 5.0 中三个比较重要的特性 : 枚举类型 , 注释类型 , 范型 , 并在此基础上介绍在如何在 Eclipse 3.1 开发环境中开发枚举类型 , 注释类型和范型应用 . 本文将介绍枚举类型 .
J2SE 5.0 以及之前的 JDK 有两种基本方法可以来定义新类型:通过 Classes 以及 Interface. 对于大部分面向对象编程来说,这两种方法看起来似乎足够了 . 但是在一些特殊情况下,这些方法就不适合 . 例如,我们想定义一个类型 Priority, 它只能接受 High, Medium, Low 三种值 . 其他任何值都是非法的 .J2SE 5.0 以前的 JDK 是可以构造这种类型的,但是需要做很多工作,有可能会带来如不安全 ( 类型安全性问题 ???) 等潜在问题,而 J2SE 5.0 的枚举类型(Enum) 能避免这些问题 .
Eclipse 是 JAVA 程序员最常用的开发平台,而 Eclipse 3.1 提供对 J2SE 5.0 的支持,它为 J2SE 5.0 的新功能提供了帮助工具 . 在对枚举类型的支持上,它不仅提供了枚举类型的创建模板,而且为枚举类型的各种开发错误提供错误提示及帮助修改 .
本文首先介绍枚举类型的创建基本概念以及如何在 Eclipse 3.1 平台上创建枚举类型,然后我们通过在 Eclipse 3.1 开发环境中的例子来说明枚举类型的应用 .
下面的例子显示了如何创建一个最基本的枚举类型:
清单 1. 枚举类型的定义
public enum Priority {High, Medium, Low };
|
它包括一个关键字 enum ,一个新枚举类型的名字 Priority 以及为 Priority 定义的一组值 .
在 Eclipse 3.1 平台上,按照下面步骤来生成枚举类型:(Eclipse 3.1 提供了一个新的枚举类型创建向导 (wizard) 以方便用户创建枚举类型 )
1) File->New->Other, 模板列表显示出来 .
2) 在模板列表上选中 Java->Enum, 点击 Next 按钮
3) 按图 1 填写每一个域 如下 :
图 1: Eclipse 3.1 枚举类型创建模板
4) 点击 Finish 按钮 , 生成 Priority 的类 ( 定义 ???), 并声明 Priority 的每一个值 , 如下图 2 所示 :(High, Medium, low 从何而来 ???)
图 2: 枚举类型 Priority
在创建枚举类型时,注意几个重要的概念 .
-
所有创建的枚举类型都扩展于 java.lang.Enum. Enum 是在 J2SE 5.0 里定义的一个新类, 它本身不是枚举类型 . 在创建枚举类型时,必须用 enum 关键字,不能直接地定义一个继承 Enum 的类来创建一个枚举类型,尽管所有创建的枚举类型实际上都是 Enum 的子类 . 如果直接继承 Enum, compiler 就会报错 ( 会导致编译错误 ). 如图 3 所示
图 3. 直接继承 Enum 类
-
枚举类型里定义的每一个值都是枚举类型的一个实例 , 比方说 High 是 Priority 的一个实例 . 枚举类型又是扩展于 Enum. 所以枚举类型的每一个值声明时, 缺省时都将映射到 Enum(String name, int ordinal) 构造函数中 . 换句话说,enum Priority {High, Medium, Low } 的实现是调用了下面的 Enum 构造函数:
清单 2 映射的构造函数调用
new Enum< Priority >("High", 0); new Enum< Priority >("Medium", 1); new Enum< Priority >("Low", 2);
每一个创建的枚举类型都是 Enum 的子类,除了上面调用父类 Enum 的构造函数外,枚举类型可以使用参数为定义一些自己的构造函数 . 当声明值时,只需调用此枚举类型定义的构造函数,而且不必添加 new 关键字 . 在清单 3 里, Priority 的一个实例生成,这个实例就是 High (38).
清单 3. 其它构造函数调用
enum Priority { High (38), Medium(36.5), Low (5.2); double temperature; Priority (double p) temperature = p; }
另外要强调的两点 : 一是这些枚举类型的构造函数都是私有的 . 它是不能被其它的类或者其它的枚举类型调用的 . 而且这个私有修饰符是由编译器自动加的 , 如果我们定义这些构造函数时 , 在前面加上 public 修饰符 , 就会导致编译错误 , 如下图 5 所示 . 二是变量定义必须在枚举类型值定义之后 . 上图中 double temperature 必须在枚举类型值定义完了 ( 分号表示枚举类型值定义完了, 如 Low(5.2);) 才能声明 .
图 4. 枚举类型的构造函数是私有的
-
在 J2SE 5.0 以前,当我们实现一个枚举类时,一般都是把一个整数关联到此枚举类的某一个值的名字,出现的问题是同一个整数可以代表不同枚举类的值 . 下面的例子里定义两个枚举类 Course and Grade 如下:
清单 4.
public class Course { public static final int EnglishLit = 1; public static final int Calculus = 2; public static final int MusicTheory = 3; public static final int MusicPerformance = 4; } public class Grade { public static final int A = 1; public static final int B = 2; public static final int C = 3; public static final int D = 4; public static final int F = 5; public static final int INCOMPLETE = 6; }
如果开发者误把 student1.assignGrade(Grade.A) 写成 student1.assignGrade(Course.EnglishList); 在编译 阶段是不能发现问题的,如果用 J2SE 5.0 枚举类型(enum)可以避免这些问题 .
- 枚举类型每一个值都是 public, static and final 的 . 也就是说,这些值是唯一的而且一旦定义了是不能被重写或修改 . 而且尽管在枚举类型每一个值声明时没有出现 static 关键字, 实际上值都是静态的 , 而且我们不能在值前面加上 static, public,final 修饰符 , 否则就会出现下图 6 的错误 .
图 5 枚举类型值的错误声明
- 枚举类型都实现了 java.lang.Comparable,枚举类型的值是可以比较排序的,排列顺序就是枚举类型定义这些值的顺序 .
下面各小节介绍了枚举类型的各种应用 .
1.3.1 循环(Iteration)
当我们写程序时,常常遇到对数组或列表里的每一个对象进行处理的情况 . 在 J2SE 5.0 以前,如果要在一个数组或列表里进行轮循时,我们的做法比较繁琐,需要借助 java.util.Iterator 类, 如下所示:
清单 5:
List priorities = Priority.values().;
for (Iterator iter = priorities.iterator(); iter.hasNext();) {
Priority p = (Priority) iter.next();
process(p);
}
|
现在我们可以通过 J2SE 5.0 的 for/in loop 和枚举类型一起使用 . 这能使以前花很多时间写的程序简单化,如上面清单 5 的程序可简化为:
清单 6:
for (Priority g: Priority.values()){
process(g);
}
|
我们把上面的伪代码写成程序在 Eclipse3.1 上运行,如下图所示,在右下控制平台视图里显示了运行结果 . 如果看不见控制平台,点击 Window->Other Views->Console, 控制平台就会出现在右下角 .
图 6 枚举类型在循环中的应用
我们在使用 for/in loop 时要求它的表达式要求必须是数组或者是实现了 java.lang.Iterable 的集合,而枚举类型的 values() 函数返回的就是一个数组 . 另外循环变量的声明必须是在 loop 里 , 包括变量类型和变量名 .
我们不能在循环里使用一个在循环之外声明的变量 . 这和 J2SE 5.0 以前 for loop 里用的循环变量的声明不同 .
1.3.2 转换(Switch)
我们常用的一种判断语句就是 Switch-case 语句 . 在 Switch 语句中使用枚举类型,不仅能简化程序,而且增强了程序的可读性 .
清单 8.
File1: Task.java
public class Task {
Priority myPriority;
public Task (Priority p) {
myPriority=p;
}
public Priority getPriority(){
return myPriority;
}}
File2: TestSwitch.java
public class TestSwitch (
Task task = new Task(Priority.Medium);
switch (task.getPriority( )) {
case High:
//do case High
break;
case Midum: // fall through to Low
case Low:
//do case Low
break;
default: throw new AssertionError("Unexpected enumerated value!");
}
}
|
在 Switch 语句里使用枚举类型时,一定不能在每一个枚举类型值的前面加上枚举类型的类名,否则编译器就会报错 ( 会导致编译错误 ???). 我们把上面的程序稍作修改,在 case 语句里加上枚举类型的类名并运行在 Eclipse 3.1 平台上 . 我们发现 Eclipse 的问题视图里提示 case 语句里枚举类型值的前面加上枚举类型的类名是错误的, 如下图 8 所示 .
图 7: case 语句里枚举类型的值
原因是 J2SE 5.0 的实现要求 case 语句里每一个枚举类型值是不能有枚举类型类作为前缀的 . 前面谈到过每一个枚举类型的值都是枚举类型的一个实例 . 那么当编译器编译 case 语句时 , 是如何处理这些实例的 ? 这有两种情况:如果 switch 与枚举类型定义在同一个编译单元 , 第一次编译时一个新表会创建在内存里 . 在这个表里 , 每一个枚举类型的值都和它在枚举类型里定义的顺序关联起来 . 编译器编译结果就和下面清单 9 显示的的程序很像 . 只不过顺序号没有加到程序里 , 而是编译器在表里快速查询 . 如果枚举类型被修改或从定义 , 表会被更新 .
清单 9:
public class TestSwitch (
Task task = new Task();
switch (task.getPriority( )) {
case 0:
//do case High
break;
case 1: // fall through to Low
case 2:
//do case Low
break;
default: throw new AssertionError("Unexpected enumerated value!");
}
}
|
还有一种经常出现的情况是 switch 与枚举类型定义不是在同一个编译单元 . 在这种情况下 , 大多数编译器就会把 switch-case 语句翻译成一系列的 if/else 语句 :
清单 10:
Priority tmp = task.getPriority( );
if (tmp == High)
//do case High
else if (tmp == Midium)
else if (tmp == Low)
//do case Low
else {
throw new AssertionError("Unexpected enumerated value!");
}
|
1.3.3 Maps of Enum and Sets of Enum
在 J2SE 5.0 的 java.util 程序包中提供两个新类:EnumMap 和 EnumSet,这两个类与枚举类型的结合应用可使以前非常繁琐的程序变得简单方便 .EnumMap 类提供了 java.util.Map 接口的一个特殊实现,该接口中的键(key)是一个枚举类型 .
清单 11:. EnumMap 例子
public void test() throws IOException {
EnumMap<Priority, String> descriptionMessages =
new EnumMap< Priority, String>( Priority.class);
descriptionMessages.put(Priority.High, "High means ...");
descriptionMessages.put(Priority.Medium, " Medium represents...");
descriptionMessages.put(Priority.Low, " Low means...");
for (Priority p : Priority.values( ) ) {
System.out.println("For priority " + p + ", decription is: " +
descriptionMessages.get(p));
}
}
|
EnumSet 类提供了 java.util.Set 接口的实现,该接口保存了某种枚举类型的值的集合 .EnumSet 的作用类似于特性的集合,或者类似于某个枚举类型的所有元素的值的子集 .EnumSet 类拥有一系列的静态方法,可以用这些方法从枚举类型中获取单个元素或某些元素, 下面的程序例子显示如何这些静态方法:
清单 12:.EnumSet 例子
public class TestEnumSet {
public enum ColorFeature {
RED,BLUE, GREEN, YELLOW,BLACK
} ;
public static void main(String[] args) {
EnumSet allFeatures = EnumSet.allOf(ColorFeature.class);
EnumSet warmColorFeatures = EnumSet.of(ColorFeature.RED,
ColorFeature.YELLOW);
EnumSet non_warmColorFeatures = EnumSet.complementOf(warmColorFeatures);
EnumSet notBlack = EnumSet.range(ColorFeature.RED, ColorFeature.YELLOW);
for (ColorFeature cf : ColorFeature.values()){
if (warmColorFeatures.contains(cf)) {
System.out.println("warmColor "+cf.name());
}
if (non_warmColorFeatures.contains(cf)) {
System.out.println("non_WarmColor "+cf.name());
}
}
}
}
|
我们在 Eclipse3.1 环境中运行上面的程序,结果如下图:
图 8: EnumSet 样例运行结果
1.3.4 枚举类型的函数定义
在介绍创建枚举类型中曾提到枚举类型都是 java.lang.Enum 的子类 . 也就是说 , 枚举类型都是可编译的 Java 的类,那么就可以在枚举类型里添加构造函数和其它函数,如清单 13 里的 getDescription()
清单 13:
public enum ColorFeature {
RED(0),
BLUE(0),
GREEN(300),
YELLOW(0),
BLACK(0);
/** The degree for each kind of color*/
private int degree;
ColorFeatures(int degree) {
this.degree = degree;
}
public int getDegree( ) {
return degree;
}
public String getDescription( ) {
switch(this) {
case RED: return "the color is red";
case BLUE: return "the color is blue";
case GREEN: return "the color is green";
case BLACK: return "the color is black";
case YELLOW: return "the color is yellow"
default: return "Unknown Color";
}
}}
|
枚举类型的函数定义的应用是很有用的, 例如可以让多个枚举类型实现同一个 interface 来达到程序设计的模式化 . 例如一个定义了 getDescription ()接口的 interface, 让有同样需求的不同枚举类型来实现它 . 上面的 colorFeature 可以实现它 , 另一个 FontFeature 也可以实现它 .
1.3.5 特定于常量的类主体
在上一节里提到枚举类型可以定义自己的函数,其实更进一步,枚举类型的每一个值都可以实现枚举类型里定义的抽象函数,这听起来很不可思议,我们可以先看下面的例子 .
public enum Priority implements Feature {
High (38) {
public void perform() {
System.out.println("high 38");
}
},
Medium(36.5) {
public void perform() {
System.out.println("medium 36.5");
}
},
Low (5.2){
public void perform() {
System.out.println("low 5.2");
}
};
public abstract void perform();
public String getDescription(Priority p) {
return null;
}
}
|
枚举类型 Priority 定义了一个抽象函数 perform(),Priority 的每一个值都对 perform 函数实现了重载,这就是枚举类型的特定于常量的类主体 . 在这种情况下,每声明一个值,枚举类型的一个子类生成,然后生成这个子类的唯一的实例来表示这个值 . 不同的值,就有对应的不同的子类 . 每个子类可以对父类的抽象函数进行重载 . 我们可以用下面的程序在 Eclipse3.1 环境中运行来证明此时 3 个子类生成 .
public class Task {
Priority myPriority;
public Task (Priority p) {
myPriority=p;
}
public Priority getPriority(){
return myPriority;
}
public void test() throws IOException {
if (myPriority == Priority.High)
System.out.println(Priority.High.getClass().getName());
if (myPriority == Priority.Medium)
System.out.println(Priority.Medium.getClass().getName());
if (myPriority == Priority.Low)
System.out.println(Priority.Low.getClass().getName());
}}
public class TestSwitch {
public static void main(String[] args) {
Task task = new Task(Priority.High);
Task task1 = new Task(Priority.Medium);
Task task2 = new Task(Priority.Low);
try {
task.test();
task1.test();
task2.test();
} catch (IOException e) {
e.printStackTrace();
}
}
|
运行结果如下图 10.
图 9 测试特定于常量的类主体运行结果
由于特定于常量的类主体难理解容易出错,它的应用比较少,大多数情况下可以用 switch-case 语句替代 . 在这里简单介绍,仅供参考 .
使用枚举类型是很简单的 . 它定义一个固定的、封闭的值集合,然后,在需要这些值中的某一个值时,可以通过它的名称来指定它,这就是枚举类型的简单性 . 枚举类型的值就是枚举类型的实例,编译器会确保没有传入其他的类型,这就是枚举类型的安全性 . 这些枚举类型就是类本身,因此,可以对类进行的所有操作同样可以作用于枚举类型上 . 我们要小心使用构造函数和函数重载方法,不要因为这些特性可用就任意使用它们 . 比如特定于常量的类主体,大多情况下可以用 Switch 语句来代替,更容易让人理解而且不容易出错 . 我们也看到了 Eclipse 3.1 平台对枚举类型的支持,包括提供创建模板,错误信息提示等 . 总之,枚举类型的灵活应用能极大的方便和简化了我们的开发工作 .
- 从 www.eclipse.org下载 Eclipse3.1, 从 java.sun.com下载 J2SE5.0 .
- 请参阅 Enum、 EnumMap和 EnumSet,Annotation,Template类的 Javadocs.
- 请参阅 Sun 的 J2SE 5.0 文档中的 type-safe enums.
- 请参阅 John Zukowski撰写的文章 驯服 Tiger: 深入研究枚举类型
- 请参阅 The Java tutorial 中有关范型的部分 ()
- 请参阅 Using and programming Generics in J2SE 5.0
- 请参阅 Preparing for Generics
- 请参阅 Java 语言与 Generics
- 请参阅 Java: The Complete Reference, J2SE 5th Edition

邹青, 2002年毕业于加拿大皇后大学, 2004加入IBM中国软件开发中心(CSDL)普及运算(pervasive computing)部门. 她主要擅长于移动计算技术, Java技术 (J2EE, JVM), Eclipse技术, 嵌入式Linux 技术. 您可以通过 qingzou@cn.ibm.com和她联系.

吴嫣,获得清华大学计算机系工学学士学位,以及新加坡国立大学新加坡麻省理工联合项目的计算机硕士学位.目前在IBM中国软件开发中心(CSDL),普及运算(pervasive computing)小组任软件工程师. 她主要兴趣在嵌入式以及分布式计算等领域. 您可以通过 wuyan@cn.ibm.com和她联系 .

吴疆,2003年毕业于清华大学, 同年加入IBM中国软件开发中心(CSDL), 现在在普及运算(pervasive computing)部门任软件工程师. 他主要集中在移动计算技术, Java技术, Eclipse技术, Linux技术. 您可以通过 wujiangj@cn.ibm.com和他联系.