枚举类入门
在某些情况下,一个类的对象是有限且固定,比如季节类,它只有4个对象;再比如行星类,目前只有8个对象。这种实例有限而且固定的类,在Java里被称为枚举类。
Java5新增了一个enum关键字(它与class,interface关键字的地位相同),用以定义枚举类。正如前面所看到的,枚举类是一种特殊的类,它一样可以有自己的成员变量、方法,可以实现一个或者多个接口,也可以定义自己的构造器。一个Java源文件最多只能定义一个public访问权限的枚举类,且该Java源文件也必须和该枚举类的类名相同。
但枚举类终究不是普通类,它与普通类有如下简单区别。
- 枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是默认继承Object类,因此枚举类不能显式继承其它父类。其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口。
- 使用enum定义非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。
- 枚举类的构造器只能使用private访问控制符,如果省略了构造器的访问控制符,则默认使用private修饰;如果强制指定访问控制符,则只能指定private修饰符。
- 枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例。列出这些实例时,系统会自动添加public static final修饰,无须程序员显式添加。
额外说明:枚举类默认提供了一个values()方法,该方法可以很方便地遍历所有的枚举值。如下所示,其中SeasonEnum 为下面定义的枚举类
for(SeasonEnum s:SeasonEnum.values()){
sout(s);
}
下面程序定义了一个SeasonEnum枚举类。
public enum SeasonEnum{
// 在第一行列出4个枚举实例
SPRING,SUMMER,FALL,WINTER;
}
编译上面Java程序,将生成一个SeasonEnum.class文件,这表明枚举类是一个特殊的Java类。由此可见,enum关键字和class、interface关键字的作用大致类似。
定义枚举类时,需要显式列出所有的枚举值,如上面的SPRING,SUMMER,FALL,WINTER;所示,所有的枚举值之间以英文逗号(,)隔开,枚举值列举结束后以英文分号作为结束。这些枚举值代表了该枚举类的所有可能的实例。
如果需要使用该枚举类的某个实例,则可以使用EnumClass.variable的形式,如SeasonEnum.SPRING。也可以调用方法valueOf,具体如下:
SeasonEnum seasonEnum1 = Enum.valueOf(SeasonEnum.class,"SPRING");
SeasonEnum seasonEnum2 = SeasonEnum.valueOf("SPRING");
SeasonEnum seasonEnum3 = SeasonEnum.SPRING;
枚举类的成员变量、方法和构造器
枚举类也是一种类,只是它是一种比较特殊的类,因此它一样可以定义成员变量、方法和构造器。下面程序将定义一个Gender枚举类,该枚举类里包含了一个name实例变量。
public enum Gender{
MALE,FEMALE;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
对于里面的实例变量name,我们可以通过获取Gender枚举类的对象来进行调用,如下。
public class GenderTest{
public static void main(String[] args){
// 这里获取Gender对象(实例)使用上述3个方法都可以
Gender g = Enum.valueOf(Gender.class,"MALE");
g.setName("男");
}
}
但是上述代码依然有可能存在问题,如获取的是MALE对象,但是设置name时却设置成了女。这样就存在问题了。同时枚举类通常应该设计成不可变类,也就是说,它的成员变量值不应该允许改变,这样会更安全,而且代码更加简洁。因此建议将枚举类的成员变量都使用private final修饰。
如果将所有的成员变量都使用了final修饰符来修饰,所以必须在构造器里为这些成员变量指定初始值(或者在定义成员变量时指定默认值,或者在初始化块中指定初始值,但这两种情况并不常见),因此应该为枚举类显式定义带参数的构造器。
一旦为枚举类显式定义了带参数的构造器,列出枚举值时就必须对应地传入参数。
public enum Gender{
// 此处的枚举值必须调用对应的构造器来创建
MALE("男"),FENALE("女");
private final String name;
// 枚举类的构造器只能使用private修饰
private Gender(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
从上面程序中可以看出,当为Gender枚举类创建了一个Gender(String name)构造器之后,列出枚举值就应该采用第三行代码来完成。也就是说,在枚举类中列出枚举值时,实际上就是调用构造器创建枚举类对象,只是这里无须使用new关键字,也无须显式调用构造器。前面列举出枚举值是无须传入参数,甚至无须括号,仅仅是因为前面的枚举类包含无参数的构造器。
不难看出,上面程序第三行的代码实际上等同于如下两行代码:
public static final Gender MALE = new Gender("男");
public static final Gender FEMALE = new Gender("女");
实现接口的枚举类
这里主要是想介绍下每个枚举值(或者说是枚举实例)实现接口方法可以表现出不同的行为方式。代码示例如下。
public enum Gender implements GenderInfo{
// 此处的枚举值必须自动阿勇对应的构造器来创建
MALE("男"){
public void info(){
sout("这个枚举值代表男性");
}
},
FEMALE("女"){
public void info(){
sout("这个枚举值代表女性");
}
}
}
上面代码看起来有些奇怪:当创建MALE和FEMALE两个枚举值时,后面又紧跟了一堆花括号,这对花括号包含了一个info()方法定义。如果读者还记得匿名内部类语法的话,则可能对这样的语法有点印象了,花括号部分实际上就是一个类体部分,在这种情况下,当创建MALE、FEMALE枚举值时,并不是直接创建Gender枚举类的实例,而是相当于创建Gender的匿名子类的实例。因为上面代码75-77和80-82行实际上是匿名内部类的类体部分,所以这个部分的代码语法就和匿名内部类语法大致相似,只是它依然是枚举类的匿名内部子类。
编译上面的程序,可以看到生成了Gender.class、Gender$1.class和Gender$2.class三个文件,这样的三个class文件正好证明了上面的结论:MALE和FEMALE实际上是Gender匿名子类的实例,而不是Gender类的实例。
特别说明:并不是所有的枚举类都使用了final修饰!非抽象的枚举类才默认使用final修饰。对于一个抽象的枚举类而言----只要它包含了抽象方法,它就是抽象枚举类,系统会默认使用abstract修饰,而不是使用final修饰。因此枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类,因为系统已自动为它添加了abstract关键字,但因为枚举类需要显式创建枚举值(枚举实例),而不是作为父类,所以定义每个枚举值(枚举实例)时必须为抽象方法提供实现,否则将出现编译错误。
本文主要参考自疯狂java讲义第四版中关于枚举讲解的内容,如若侵权,请联系删除。