1.3 枚举类型的应用
下面各小节介绍了枚举类型的各种应用.
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 语句替代. 在这里简单介绍,仅供参考.
1.4 枚举类型的小结
使用枚举类型是很简单的.它定义一个固定的、封闭的值集合,然后,在需要这些值中的某一个值时,可以通过它的名称来指定它,这就是枚举类型的简单性.枚举类型的值就是枚举类型的实例,编译器会确保没有传入其他的类型,这就是枚举类型的安全性.这些枚举类型就是类本身,因此,可以对类进行的所有操作同样可以作用于枚举类型上.我们要小心使用构造函数和函数重载方法,不要因为这些特性可用就任意使用它们.比如特定于常量的类主体,大多情况下可以用Switch语句来代替,更容易让人理解而且不容易出错.我们也看到了Eclipse 3.1平台对枚举类型的支持,包括提供创建模板,错误信息提示等.总之,枚举类型的灵活应用能极大的方便和简化了我们的开发工作。