枚举类
理解枚举类时,关键要理清:枚举类
Enum
只是可实例化的对象有限且固定的普通Java类class
。因为其实例为枚举的,所以其可枚举的对象需在枚举类定义时即进行声明,除此之外,不必认为枚举有何本质的特殊之处。
1. 概念引入
有时候,一个类的对象是有限且固定的,比如季节类只有 4 个对象,行星类只有 8 个对象,这种实例有限且固定的类,在 Java 里被称为枚举类。
Java 5 新增了enum
关键字用来定义枚举类,enum
关键字与 class
, interface
的地位相同,枚举类属于一种特殊的类,但它与class
又没有本质区别,枚举类主要涉及如下特点:
- 定义的
enum
类型总是继承自java.lang.Enum
,且无法被继承; - 只能定义出
enum
的实例,而无法通过new
操作符创建enum
的实例——枚举类的构造方法只能是private
的; - 定义的每个实例都是引用类型的唯一实例;
- 枚举类的所有实例(即枚举值)必须在枚举类的第一行显式列出,否则这个枚举类永远不能产生实例,且这些实例被
public static final
修饰,程序员无须显式添加; - 枚举类默认提供了一个
values()
方法,可以方便地遍历所有的枚举值; - 可以将
enum
类型用于switch
语句。
注意枚举类虽然不能继承其他父类,但可以实现一个或多个接口。如java.lang.Enum
类就实现了 java.io.Serializable
和 java.lang.Comparable
两个接口。
1.1 枚举类示例1
例如,定义一个Color
枚举类:
public enum Color {
RED, GREEN, BLUE;
}
编译器编译出的class
大概就像这样:
public final class Color extends Enum { // 继承自Enum,标记为final class
// 每个实例均为全局唯一:
public static final Color RED = new Color();
public static final Color GREEN = new Color();
public static final Color BLUE = new Color();
// private构造方法,确保外部无法调用new操作符:
private Color() {}
}
所以,编译后的enum
类和普通class
并没有任何区别。但是我们自己无法按定义普通class
那样来定义enum
,必须使用enum
关键字,这是Java语法规定的。
1.2 枚举类示例2
以下为另一个枚举类的应用,用来说明枚举类与普通类并没有太大的区别,差别只是产生枚举对象的方式不同——枚举类的实例只能是枚举值,而不是随意地通过new
来创建枚举类对象:
enum Gender {
MALE, FEMALE;
private String name; // 一个寻常的实例属性
// 一个寻常的set方法
public void setName(String name) {
switch (this) {
case MALE:
if (name.equals("男"))
this.name = name;
else {
System.out.println("参数错误");
return;
}
break;
case FEMALE:
if (name.equals("女"))
this.name = name;
else {
System.out.println("参数错误");
return;
}
break;
}
}
// 一个寻常的get方法
public String getName() {
return this.name;
}
}
class GenderTest {
public static void main(String[] args) {
Gender g = Gender.valueOf("FEMALE");
g.setName("女");
System.out.println(g + "代表:" + g.getName());
// 此时设置name值时将会提示参数错误
g.setName("男");
System.out.println(g + "代表:" + g.getName());
}
}
2. 枚举类的方法
基类 java.lang.Enum
中提供了如下几个方法,所有枚举类都可以直接使用:
注意,
compareTo, name, ordinal
方法都是final
的,不可以被子类覆盖。
方法 | 作用 |
---|---|
public final int compareTo(E o) | 与相同枚举类型的枚举对象比较顺序。 1) 若该枚举对象位于指定枚举对象之后返回正整数; 2) 若该枚举对象位于指定枚举对象之前返回负整数; 3) 否则返回零。 |
public final String name() | 返回此枚举值的名称 |
public final int ordinal() | 返回枚举值在枚举类中的索引值,即枚举值在枚举声明中的位置 |
public String toString() | 返回枚举常量的名称,默认实现与 name() 相同。不同之处在于toString 可以被子类覆盖。 |
public static<T extends Enum<T>> T valueOf(Class<T> enumType, String name) | 返回指定枚举类中指定名称的枚举值,相当于toString()的逆方法。 |
在自定义的枚举类中,即Enum
的子类中,valueOf
方法的签名为:
public static T valueOf(String name) throws IllegalArgumentException
- 即只需要一个字符串
name
参数,即可返回一个枚举实例,这里的name
参数为实例的名称。
3. 枚举类的判等
使用enum
定义的枚举类是一种引用类型。而对于引用类型的判等,一般要用equals()
方法,因为当使用==
时它比较的是两个引用类型的变量是否是同一个对象,不过,enum
类型可以例外。
这是因为enum
类型的每个常量在 JVM 中只有一个唯一实例,所以可以直接用==
比较:
public class EnumTest {
@Test
public void test1() {
Season spring1 = Season.SPRING;
Season spring2 = Season.valueOf("SPRING");
System.out.println(spring1 == spring2); // true
System.out.println(spring1.equals(spring2)); // true, this is ok but more codes!
}
}
enum Season {
SPRING("spring"), SUMMER("summer"), AUTUMN("autumn"), WINTER("winter");
private final String type;
Season(String type) {
this.type = type;
}
}
4. 带参数的枚举类构造器
枚举类的构造器总是私有的。
带参数的枚举类构造器,列出枚举值时必须对应地传入参数:
enum Gender {
// 此处的枚举值必须调用对应的构造器来创建
// 以下第一行相当于:
// public static final Gender MALE = new Gender("男");
// public static final Gender FEMALE = new Gender("女");
MALE("男"), FEMALE("女");
private final String name;
// 枚举类的构造器只能使用private修饰
private Gender(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
5. 实现接口的枚举类
如果由枚举类来实现接口里的方法,则每个枚举值在调用该方法时都有相同的行为方式(因为方法体一样);而如果需要每个枚举值在调用方法时呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法,每个枚举值提供不同的实现方式,从而让不同的枚举值调用该方法时具有不同的行为方式。
interface GenderDesc {
void info();
}
enum Gender implements GenderDesc {
// 此处的枚举值必须调用对应的构造器来创建
// 花括号部分实际上是一个类体部分
MALE("男") {
public void info() {
System.out.println("这个枚举值代表男性");
}
},
FEMALE("女") {
public void info() {
System.out.println("这个枚举值代表女性");
}
};
private final String name;
private Gender(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
6. 包含抽象方法的枚举类
枚举类里定义抽象方法时不能使用abstract
关键字将枚举类定义成抽象类(因为系统会自动添加),但因为枚举类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。