枚举类型是指由一组固定的常量组成合法值的类型,例如一年中的季节或一副牌中的花色。在没引入枚举时,一般是声明一组int常量,每个类型成员一个常量:
publicstaticfinalintAPPLE_FUJI= 0;
publicstaticfinalintAPPLE_PipPIN= 1;
publicstaticfinalintAPPLE_GRANNY_SMITH= 2;
publicstaticfinalintORANGE_NAVEL= 0;
publicstaticfinalintORANGE_TEMPLE= 1;
publicstaticfinalintORANGE_BLOOD= 2;
这种方法称作int枚举模式,存在很多不足,不具有类型安全与使用方便性。如果你将apple传到一个想要接收orange的方法中,编译器也不会出现警告,而且还可以使用==来比较apple与orange。
注意每个apple常量都以APPLE_作为前缀,每个orange常量都以ORANGE_作为前缀,这是因为可以防止名称冲突。
采用int枚举模式的程序是十分脆弱,因为int枚举是编译时常量,被编译到使用它们的客户端中。如果与枚举常量关联的int发生了变化,客户端就必须重新编译,如果不重新编译,程序还是可以运行,但不是最新的值了。
另外从使用方便性来看,没有便利的toString方法,打印出来的为数字,没有多大的用处。要遍历一组中所有的int枚举常量,也没有可靠的方法。
既然int枚举常量有这么多的缺点,那使用String枚举常如何?同样也不是我们期望的。虽然在可以打印字符串,但它会导致性能问题,因为它依赖于字符串的比较操作。另外与int枚举常量一样会编译到客户端代码中,编译时难以发现,但会在运行时出错。
幸运的是1.5版本开始,枚举可以避免int和String枚举模式的缺点,并提供许多额外的好处。下面是最简单的形式:
publicenumApple{FUJI,PIPPIN,GRANNY_SMITH}
publicenumOrange{NAVEL,TEMPLE,BLOOD}
Java枚举类型背后的基本想法很简单:本质上是int值,它们是通过公有的静态final域为每个枚举常量导出实例的类。因为没有可以访问的构造器,枚举类型是真正的final。因为客户端即不能创建枚举类型的实例,也不能对它进行扩展,因此对它进行实例化,而只有声明过的枚举常量。换句话说,枚举类型是实例受控的。它们是单例的泛型化,本质上是单元素的枚举。
枚举提供了编译时类型安全。如果声明一个参数的类型为Apple,就可以保证,被传到该参数上的任何非null的对象引用一定属于三个有效的Apple值之一。试图传递类型错误的值时,会导致编译时错误,就像试图将某种枚举类型的表达式赋值给另一种枚举类型的变量,或者试图利用==操作符比较不同枚举类型的值一样,都会出错。
枚举提供了单独的命名空间,同一系统中可以有多个同名的枚举类型变量。你可以增加或者重新排序枚举类型常量,而无需重新编译它的客户端代码,因为导出常量的域在枚举类型和它的客户端之间提供了一个隔离层:常量值并没有被编译到客户端代码中,而是在int枚举模式之中。最终,可以通过调用toString方法,将枚举转换成可打印的字符串。
除了完善了int枚举模式不足外,枚举还允许添加任意的方法和域,并实例任意接口,它们提供了所有Object(见第3章)的高级实现,实现了Comparable和Serializable接口,并针对枚举型的可任意改变性设计了序列化方式。
如果一个枚举具有普遍适用性,它就应该成为一个顶层类,如果它只是被用在一个特定的顶层类中,它就应该成为该顶层类的一个成员类。
可以为枚举类型添加数据域与方法,下面是一个算术运行的枚举类:
publicenumOperation {
PLUS("+") {
doubleapply(doublex,doubley) {
returnx + y;
}
},
MINUS("-") {
doubleapply(doublex,doubley) {
returnx - y;
}
},
TIMES("*") {
doubleapply(doublex,doubley) {
returnx * y;
}
},
DIVIDE("/") {
doubleapply(doublex,doubley) {
returnx / y;
}
};
PRivatefinalString symbol;//操作符:+ - * /
Operation(String symbol) {//构造函数,存储操作符供toString打印使用
this.symbol = symbol;
}
@Override
//重写Enum中的打印name的性为
publicString toString() {
returnsymbol;
}
//抽像方法,不同的常量具有不同的功能,需在每个常量类的主体里重写它
abstractdoubleapply(doublex,doubley);
/*
* 初始化时,存储操作符与枚举常量的对应关系,用来实现fromString方法
* 这样我们就可以通过操作符来获取到对应的枚举常量,有点像valueOf方法,
* 只不过它是通过枚举常量的名字name来获取常量的。这种通用的方法还可以
* 应用到其他枚举类中
*/
privatestaticfinalMap<String, Operation>stringToEnum=newHashMap<String, Operation>();
static{ //从name到枚举常量转换到从某个域到枚举常量的转换
for(Operation op :values())
stringToEnum.put(op.toString(), op);
}
//根据操作符来获取对应的枚举常量,如果没有返回null,模拟valueOf方法
publicstaticOperation fromString(String symbol) {
returnstringToEnum.get(symbol);
}
publicstaticvoidmain(String[] args) {
doublex = Double.parseDouble(args[0]);
doubley = Double.parseDouble(args[1]);
for(Operation op : Operation.values())
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
for(Operation op : Operation.values())
System.out.printf("%f %s %f = %f%n", x, op, y, Operation
.fromString(op.toString()).apply(x, y));
}
}
在opr包下会看见Operation.class、Operation$4.class、Operation$2.class、Operation$3.class、Operation$1.class这样几个类,Operation$X.class都是继承自Operation类,而Operation又继承自Enum类,下面是反编译这些类的代码:
public abstract class opr.Operation extends java.lang.Enum{
Word-spacing: 0px; text-transform: none; word-break: normal; margin: 0cm 0cm 0pt; letter-spacing: normal; line-height: normal; text-indent: 0px; -webkit-text-size
新闻热点
疑难解答