枚举类的使用情景
在一些情况下,一个类的对象是有限而且固定的,常见的例子如指南针的方向(EAST、SOUTH、WEST、NORTH)以及一个星期中的天数,为了表示这些事物,一般的做法是定义相应数量的静态常量,例如:
public static final int DIRECTION_EAST = 0;
public static final int DIRECTION_SOUTH = 1;
public static final int DIRECTION_WEST = 2;
public static final int DIRECTION_NORTH = 3
但是这种做法有如下缺点:
- 类型不安全,每个静态常量实际上都可以当作一个 int 型进行使用
- 没有命名空间,使用方向的时候,还需要在每个方向的前面加上 DIRECTION 前缀
- 打印输出的意义不明确,输出每个季节的时候实际上输出的是一个 int 型数据
所以对于这种情况而言,Java 5 新增了 enum 关键字用于定义枚举类。一个简单的枚举类定义如下:
public enum SeasonEnum {
SPRING, SUMMER, FALL, WINTER;
}
枚举类和普通类的区别
枚举类实际上是一种特殊的类,相较之普通的类而言,其主要的区别在于:
- 由于所有枚举类都是 Enum 类的子类,所以枚举类不能够继承其它的类
- 使用 enum 定义、非抽象的枚举类默认使用 final 修饰符,所以非抽象的枚举类不能够派生子类
- 定义的枚举类可以为空,但是一旦定义成员,必须在首行列出所有可能的实例值,而且默认用 public static final 进行修饰
- 枚举类的构造器只能够使用 private 修饰符进行修饰
枚举类的成员
枚举类的成员可以是成员变量、方法、构造器、初始化块以及内部类和接口。
初始化块、内部类和接口以及静态变量很少使用,个人觉得不使用初始化块的原因在于枚举类的大部分成员变量是用 private final 进行修饰,而且对于不同的实例表示变量值一般不同,这就需要用到有参的构造函数进行传值,那么这个时候使用初始化块对变量进行初始化会造成冲突。不使用内部类和接口以及静态变量的原因可能是破坏了枚举类的作用,枚举类是用于列举出该类所有可能的实例,如果定义了静态变量,在使用枚举类.枚举名访问枚举值的时候,会出现 4 个季节有 5 个选择的情况。
接下来说说成员变量、方法、构造器可以使用的修饰符:
- 成员变量:private、protected、public 都可以使用,但是为了使枚举类成为不可变类,所以一般使用 private final 进行修饰
- 构造器:只能 private,省略了系统给你补上
- 方法:private、protected、public 都可以
枚举类的常用方法
枚举类作为 Enum 的子类,其方法结构是:
接下来分别说说常用的方法及其作用:
- values():返回枚举类的所有实例,一般用于遍历
for (SeasonEnum season : SeasonEnum.values()) {
System.out.println(season);
}
输出结果:
SPRING
SUMMER
FALL
WINTER
- name() :返回枚举实例的名称,大多时候应该使用 toString() 方法,因为 toString() 返回更加友好的名称
System.out.println(SeasonEnum.SPRING.name());
输出结果:
SPRING
- oridinal():返回枚举值在枚举类中的索引值(就是枚举值在枚举类中声明的位置)
System.out.println(SeasonEnum.SPRING.oridinal());
输出结果:
0
toString():返回枚举值常量的名称,与 name 方法类似
valueOf(): 用于返回枚举类中指定的枚举值
System.out.println(Enum.valueOf(SeasonEnum.class, "SUMMER"));
输出结果:
SUMMER
枚举类的使用
- 定义了成员变量、方法以及构造器的枚举类,枚举类最好是设计成不可变的类,所以一种比较好的实现方式是:
public enum SeasonEnum {
SPRING("春"), SUMMER("夏"), FALL("秋"), WINTER("冬");
private final String name;
SeasonEnum(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
通过这种方式创建的枚举类,一旦列出枚举值,其 name 字段就再不不可更改,从而保证了枚举类不可变。
一旦为枚举类定义了带参的构造器,列出枚举值的时候就必须传入对应的参数
- 实现接口的枚举类
定义一个输出季节的接口:
public interface SeasonInterface {
void printSeason();
}
在上述枚举类的基础上实现接口,有两种方式,一种和普通类实现接口方式,另一种则是为每个枚举值实现不同的方法以表现出不同的特征:
public enum SeasonEnum implements SeasonInterface{
SPRING("春") {
@Override
public void printSeason() {
System.out.println("春天到了");
}
},
SUMMER("夏") {
@Override
public void printSeason() {
System.out.println("夏天到了");
}
},
FALL("秋") {
@Override
public void printSeason() {
System.out.println("秋天到了");
}
},
WINTER("冬") {
@Override
public void printSeason() {
System.out.println("冬天到了");
}
};
private final String name;
SeasonEnum(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
这里的方法实现方式类似匿名内部类的语法,实际上这种情况下在创建枚举值的时候并不是直接创建的 SeasonEnum 的实例,而是创建的是 SeasonEnum 的匿名子类的实例,之所以能够派生子类的原因在于抽象枚举类是用 abstract 进行修饰的而不是 final 。对于一个枚举类而言只要包含了抽象方法,就会默认使用 abstract 进行修饰。
反编译之后的类的声明部分如下:
public abstract class SeasonEnum extends java.lang.Enum<SeasonEnum> implements SeasonInterface