Jakob Jenkov 2018-09-13
Java 枚举(Enum)是一种特殊的 Java 类型,用于定义常量集合。更准确地说,Java 枚举类型是一种特殊的 Java 类。枚举可以包含常量、方法等。Java 枚举是在 Java 5 中引入的。
枚举示例
以下是一个简单的 Java 枚举示例:
public enum Level {
HIGH,
MEDIUM,
LOW
}
注意这里使用了 enum 关键字,而不是 class 或 interface。Java 的 enum 关键字向编译器表明该类型定义是一个枚举。
你可以像下面这样引用上述枚举中的常量:
Level level = Level.HIGH;
注意,变量 level 的类型是 Level,即上面定义的 Java 枚举类型。level 变量可以取 Level 枚举中的任意一个常量值(HIGH、MEDIUM 或 LOW)。在这个例子中,level 被设置为 HIGH。
在 if 语句中使用枚举
由于 Java 枚举是常量,你经常会需要将指向某个枚举常量的变量与枚举类型中所有可能的常量进行比较。以下是在 if 语句中使用 Java 枚举的一个示例:
Level level = ... // 给 level 赋某个 Level 常量
if (level == Level.HIGH) {
// ...
} else if (level == Level.MEDIUM) {
// ...
} else if (level == Level.LOW) {
// ...
}
这段代码将 level 变量与 Level 枚举中所有可能的常量逐一比较。
如果某个枚举值出现的频率高于其他值,那么在第一个 if 语句中先检查该值可以获得更好的性能(因为平均而言执行的比较次数更少)。不过,除非这些比较被频繁执行,否则这种优化带来的性能差异并不显著。
在 switch 语句中使用枚举
如果你的 Java 枚举包含大量常量,并且你需要像上一节那样对变量进行判断,那么使用 Java 的 switch 语句可能是个好主意。
你可以像这样在 switch 语句中使用枚举:
Level level = ... // 给 level 赋某个 Level 常量
switch (level) {
case HIGH:
// ...
break;
case MEDIUM:
// ...
break;
case LOW:
// ...
break;
}
将 ... 替换为当 level 变量匹配对应 Level 常量时要执行的代码。这些代码可以是一条简单的 Java 语句、方法调用等。
枚举遍历
你可以通过调用枚举类型的静态方法 values() 来获取该枚举所有可能值组成的数组。所有枚举类型都会由 Java 编译器自动提供一个静态的 values() 方法。以下是如何遍历枚举所有值的示例:
for (Level level : Level.values()) {
System.out.println(level);
}
运行上述 Java 代码会打印出所有枚举值。输出如下:
HIGH
MEDIUM
LOW
注意,这里直接打印出了常量本身的名称。这是 Java 枚举与 static final 常量不同的一个地方。
枚举的 toString() 方法
枚举类在编译时会自动获得一个 toString() 方法。该方法返回给定枚举实例的名称字符串。例如:
String levelText = Level.HIGH.toString();
执行上述语句后,变量 levelText 的值将是字符串 "HIGH"。
枚举的打印
如果你像下面这样打印一个枚举:
System.out.println(Level.HIGH);
那么后台会自动调用 toString() 方法,因此实际打印出的是该枚举实例的文本名称。换句话说,在上面的例子中,会打印出 "HIGH"。
枚举的 valueOf() 方法
枚举类在编译时还会自动获得一个静态的 valueOf() 方法。该方法可以根据给定的字符串值获取对应的枚举实例。例如:
Level level = Level.valueOf("HIGH");
执行这行代码后,变量 level 将指向 Level.HIGH。
枚举字段
你可以为 Java 枚举添加字段。这样,每个枚举常量都会拥有这些字段。字段的值必须在定义常量时通过枚举的构造函数传入。例如:
public enum Level {
HIGH(3), // 调用构造函数,传入值 3
MEDIUM(2), // 调用构造函数,传入值 2
LOW(1); // 调用构造函数,传入值 1
private final int levelCode;
private Level(int levelCode) {
this.levelCode = levelCode;
}
}
注意,上面的 Java 枚举有一个接收 int 参数的构造函数。枚举构造函数会将该 int 值赋给字段 levelCode。在定义枚举常量时,我们向枚举构造函数传递了一个 int 值。
枚举构造函数必须是 private 的。你不能为 Java 枚举使用 public 或 protected 的构造函数。如果你没有显式指定访问修饰符,枚举构造函数默认就是 private 的。
枚举方法
你也可以为 Java 枚举添加方法。例如:
public enum Level {
HIGH(3),
MEDIUM(2),
LOW(1);
private final int levelCode;
Level(int levelCode) {
this.levelCode = levelCode;
}
public int getLevelCode() {
return this.levelCode;
}
}
你可以通过枚举常量的引用来调用枚举方法。例如:
Level level = Level.HIGH;
System.out.println(level.getLevelCode());
这段代码会打印出 3,也就是枚举常量 HIGH 对应的 levelCode 字段值。
你不仅限于编写简单的 getter 和 setter 方法,还可以创建基于枚举常量字段值进行计算的方法。如果你的字段不是 final 的,甚至可以修改字段的值(尽管这样做可能不太合适,因为枚举本应代表常量)。
枚举抽象方法
Java 枚举类也可以包含抽象方法。如果枚举类中包含抽象方法,那么枚举中的每个实例都必须实现该方法。例如:
public enum Level {
HIGH {
@Override
public String asLowerCase() {
return HIGH.toString().toLowerCase();
}
},
MEDIUM {
@Override
public String asLowerCase() {
return MEDIUM.toString().toLowerCase();
}
},
LOW {
@Override
public String asLowerCase() {
return LOW.toString().toLowerCase();
}
};
public abstract String asLowerCase();
}
注意枚举底部的抽象方法声明,以及每个枚举实例(每个常量)如何各自实现了这个抽象方法。当你需要为每个枚举实例提供不同实现时,使用抽象方法非常有用。
枚举实现接口
在某些情况下,Java 枚举可以实现 Java 接口。例如:
public enum EnumImplementingInterface implements MyInterface {
FIRST("First Value"),
SECOND("Second Value");
private String description = null;
private EnumImplementingInterface(String desc) {
this.description = desc;
}
@Override
public String getDescription() {
return this.description;
}
}
其中 getDescription() 方法来自接口 MyInterface。
使用枚举实现接口的一种常见用途是定义一组不同的 Comparator 常量,用于对对象集合进行排序。关于 Java 中的对象排序,详见 Java 集合排序教程。
EnumSet
Java 提供了一种特殊的 Java Set 实现 —— EnumSet,它比标准的 Java Set 实现能更高效地存储枚举值。创建 EnumSet 实例如下:
EnumSet<Level> enumSet = EnumSet.of(Level.HIGH, Level.MEDIUM);
创建之后,你可以像使用其他 Set 一样使用 EnumSet。
EnumMap
Java 还提供了一种特殊的 Java Map 实现 —— EnumMap,它可以使用 Java 枚举实例作为键。例如:
EnumMap<Level, String> enumMap = new EnumMap<Level, String>(Level.class);
enumMap.put(Level.HIGH, "High level");
enumMap.put(Level.MEDIUM, "Medium level");
enumMap.put(Level.LOW, "Low level");
String levelValue = enumMap.get(Level.HIGH);
枚举的其他细节
- Java 枚举会隐式继承
java.lang.Enum类,因此你的枚举类型不能再继承其他类。 - 如果 Java 枚举中包含字段和方法,则字段和方法的定义必须放在枚举常量列表之后。
- 此外,枚举常量列表必须以分号
;结尾。