JDK5之前的Java版本缺失一项特性是枚举。形式最简单地枚举(enumeration)是一些列具有名称的常量。尽管Java提供了其他一些能提供类似功能的特性,例如final变量,但是许多程序员仍然怀念枚举概念的单纯性——特别是因为大多数其他常用语言都支持枚举。从JDK5开始,枚举被添加到了Java语言中。
形式最简单的枚举,看起来和其他语言中的枚举类似。但是,这种相似性只是表面上的。因为在Java中,枚举定义了一种类类型。通过将枚举定义为类,极大地扩展了枚举的功能。例如在Java中,枚举可以具有构造函数、方法以及实例变量。
1、 枚举的基础知识
创建枚举需要使用关键字enum。例如,下面是一个简单的枚举,其中列出了各种苹果的品种:
//An enumeration of apple varieties.
enum Apple{
Jonathan,GoldenDel,RedDel,Winesap,Cortland
}
标识符Jonathan、GoldenDel等被称为枚举常量。每个枚举常量被隐式声明为Apple的公有、静态final成员。此外,枚举常量的类型是声明它们的枚举的类型,对于这个例子为Apple。因此在Java语言中,这些常量被称为是“自类型化的”(self-typed),其中的“自”是指封装常量的枚举。
定义了枚举之后,可以创建枚举类型的变量。但是,尽管枚举定义了类类型,却不能使用new实例化枚举。反而,枚举变量的声明和使用方式在许多方面与基本类型相同。例如,下面这行代码将ap声明为Apple枚举类型的变量:
Apple ap;
因为ap是Apple类型,所以只能被赋值为(或包含)在Apple枚举中定义的那些值。例如,下面这行代码将ap赋值为RedDel:
ap = Apple.RedDel;
注意在符号RedDel之前的Apple。
可以使用关系运算符“==”比较两个枚举常量的相等性。例如,下面这条语句比较ap的值和GoldenDel常量:
if(ap == Apple.GoldenDel)
枚举值也可以用于控制switch语句。当然,所有case语句使用的常量的枚举类型,都必须与switch表达式使用的枚举类型相同。例如,下面这条switch语句是完全合法的:
//Use an enum to control a switch statement.
switch(ap){
case Jonathan:
// ...
case Winesap:
// ...
}
注意在case语句中,枚举常量的名称并没有使用枚举类型的名称进行限定。也就是说,使用的是Winesap而不是Apple.Winesap。这是因为switch表达式中的枚举类型已经隐式指定了case常量的枚举类型,所以在case语句中不需要使用枚举类型的名称对常量进行限定。实际上,如果视图这么做的话,会造成编辑时错误。
 当显示枚举常量时,例如在println()语句中,会输出枚举常量的名称。例如下面这条语句:
System.out.println(Apple.Winesap);//显示名称“Winesap”
下面的程序用到了刚才介绍的所有内容,并演示了Apple枚举:
//An enumeration of apple varieties
public enum Apple {
Jonathan,GoldenDel,RedDel,Winesap,Cortland
}
public class EnumDemo {
public static void main(String[] args) {
Apple ap;
ap = Apple.RedDel;
//Output an enum value.
System.out.println("Value of ap: " + ap);
System.out.println();
ap = Apple.GoldenDel;
//Compare two enum value.
if (ap == Apple.GoldenDel)
System.out.println("ap contains GoldenDel.\n");
//Use an enum to control a switch statement.
switch (ap) {
case Jonathan:
System.out.println("Jonathan is red.");
break;
case GoldenDel:
System.out.println("Golden Delicious is yellow.");
break;
case RedDel:
System.out.println("Red Delicious is red.");
break;
case Winesap:
System.out.println("Winesap is red.");
break;
case Cortland:
System.out.println("Cortland is red.");
break;
}
/**
* 输出结果:
* Value of ap: RedDel
* ap contains GoldenDel.
* Golden Delicious is yellow.
*/
}
}
2、values()和valueOf()方法
所有枚举都自动包含两个预定义方法:values()和valueOf()。它们的一般形式如下所示:
public static enum_type [] values()
public static enum_type valueOf(String str)
values()方法返回一个包含枚举常量列表的数组,valueOf()方法返回与传递到参数str的字符串相对应的枚举常量。例如,对于前面显示的Apple枚举,Apple.valueOf(“Winesap”)的返回类型是Winesap。
下面的程序演示了values()和valueOf()方法:
public class EnumDemo2 {
public static void main(String[] args) {
Apple ap;
System.out.println("Here are all Apple constants:");
//use values();
for (Apple a : Apple.values()) {
System.out.println(a);
}
System.out.println();
//use valueOf
ap = Apple.valueOf("Winesap");
System.out.println("ap contains " + ap);
}
/**
* 输出结果:
* Here are all Apple constants:
* Jonathan
* GoldenDel
* RedDel
* Winesap
* Cortland
*
* ap contains Winesap
*/
}
3、枚举是类类型
正如前面所解释的,Java枚举是类类型。虽然不能使用new实例化枚举,但是枚举却有许多和其他类相同的功能。枚举定义了类,这为Java枚举提供了超乎寻常的功能。例如,可以为枚举提供构造函数、添加实例变量和方法,甚至可以实现接口。
需要理解的重要一点是:每个枚举常量都是岁数枚举类型的对象。因此,如果为枚举定义了构造函数,那么当创建每个枚举常量时都会调用该构造函数。此外,对于枚举定义的实例变量,每个枚举常量都有它自己的副本。例如,分析下面版本的Apple枚举:
//Use an enum of constructor,instance variable,and method.
public enum Apple {
Jonathan(10), GoldenDel(9), RedDel(12), Winesap(15), Cortland(8);
private int price;//price of each apple
//Constructor
Apple(int p) {
price = p;
}
int getPrice() {
return price;
}
}
public class EnumDemo3 {
public static void main(String[] args) {
Apple ap;
//Display price of Winesap.
System.out.println("Winesap costs " + Apple.Winesap.getPrice() + " cents.\n");
//Display all apples and price
System.out.println("All apple prices:");
for (Apple a : Apple.values()) {
System.out.println(a + " consts " + a.getPrice() + " cents.");
}
/**
* 输出结果:
* Winesap costs 15 cents.
*
* All apple prices:
* Jonathan consts 10 cents.
* GoldenDel consts 9 cents.
* RedDel consts 12 cents.
* Winesap consts 15 cents.
* Cortland consts 8 cents.
*/
}
}
这个版本的Apple枚举添加了3个内容:第1个内容是实例变量price,用于保存每种苹果的价格;第2个内容是Apple构造函数,它以苹果的价格作为参数;第3个内容是getPrice()方法,用于返回price变量的值。
当在mian()方法中声明变量ap时,对于每个特定的常量调用Apple构造函数一次。注意指定构造函数参数的方式,通过将他们放置到每个常量后面的圆括号中来加以指定,如下所示:
Jonathan(10), GoldenDel(9), RedDel(12), Winesap(15), Cortland(8);
这些数值被传递给Apple()的参数p,然后将这些值赋给price变量。再强调一次,为每个常量调用构造函数一次。因为每个枚举常量都有自己的price变量副本,所以可以调用getPrice()方法来获取指定类型苹果的价格。例如在main()方法中,通过下面的调用获取Winesap的价格:
Apple.Winesap.getPrice()
通过for循环遍历枚举可以获取所有品种的苹果的价格。因为每个枚举常量都有price变量的副本,所以与枚举常量关联的值是独立的,并且和其他常量关联的值不同。这是一个强大的功能,只有将枚举作为类实现,像Java这样,才会具有这种功能。
枚举可以提供两种甚至更多种重载形式,就像其他类那样。例如,下面版本的Apple提供了一个默认构造函数,可以将price变量初始化为-1,表明不能获得价格数据:
//Use an enum of constructor,instance variable,and method.
public enum Apple {
Jonathan(10), GoldenDel(9), RedDel, Winesap(15), Cortland(8);
private int price;//price of each apple
//Constructor
Apple(int p) {
price = p;
}
//Overloaded constructor
Apple() {
price = -1;
}
int getPrice() {
return price;
}
}
注意在这个版本中,没有为RedDel提供参数。这意味着会调用默认构造函数,并将RedDel的price变量设置为-1。
枚举有两条限制:第一,枚举不能继承其他类;第二,枚举不能是超类。这意味着枚举不能扩展。在其他方面,枚举和其他类很相似。需要记住的关键是:每个枚举常量都是定义它的类的对象。
4、枚举继承自 Enum 类
尽管声明枚举时不能继承超类,但是所有枚举都自动继承超类java.lang.Enum,这个类定义了所有枚举都可以使用的一些方法。
可以获取用于指示枚举常量在常量列表中位置的值,这称为枚举常量的序数值。通过ordinal()方法可以检索序数值,该方法的声明如下所示:
final int ordinal()
该方法返回调用常量的序数值,序数值从0开始。因此在Apple枚举中,Jonathan的序数值为0,GoldenDel的序数值为1,等等。
可以使用compareTo()方法比较相同类型的两个枚举常量的序数值,该方法的一般形式如下:
final int compareTo(enum-type e)
其中enum-type是枚举的类型,e是和调用常量进行比较的常量。请记住,调用常量和e必须是相同的枚举。如果调用常量的序数值小于e的序数值,那么compareTo()方法返回负值;如果两个序数值相同,就返回0;如果调用常量的序数值大于e的序数值,就返回正值。
可以使用equals()方法来比较枚举常量和其他对象的相等值,该方法重写了Object类定义的equals()方法。尽管equals()方法可以将枚举常量和任意其他对象进行比较,但是只有当两个对象都引用同一枚举中相同的常量时,它们才相等。如果两个常量来自不同的枚举,那么即使它们的序数值相同,equals()方法也不会返回true。
请记住,可以使用"=="比较两个枚举引用的相等性。
下面演示了ordinal()、compareTo()以及equals()方法:
public class EnumDemo4 {
public static void main(String[] args) {
Apple ap, ap2, ap3;
//Obtain all ordinal values using ordinal().
System.out.println("Here are all apple constants and their ordinal values: ");
for (Apple a : Apple.values())
System.out.println(a + " " + a.ordinal());
ap = Apple.RedDel;
ap2 = Apple.GoldenDel;
ap3 = Apple.RedDel;
System.out.println();
if (ap.compareTo(ap2) < 0)
System.out.println(ap + " comes before " + ap2);
if (ap.compareTo(ap2) > 0)
System.out.println(ap2 + "comes before " + ap);
if (ap.compareTo(ap3) == 0)
System.out.println(ap + " equals " + ap3);
System.out.println();
if (ap.equals(ap2))
System.out.println("Error!");
if (ap.equals(ap3))
System.out.println(ap + " equals " + ap3);
if (ap == ap3)
System.out.println(ap + " == " + ap3);
/**
* 输出结果
* Here are all apple constants and their ordinal values:
* Jonathan 0
* GoldenDel 1
* RedDel 2
* Winesap 3
* Cortland 4
*
* GoldenDelcomes before RedDel
* RedDel equals RedDel
*
* RedDel equals RedDel
* RedDel == RedDel
*/
}
}