Java Enum 笔记
很多人初学 Java 时,把枚举简单理解为"一个安全的常量集合"。这没错,但远远不够。枚举的本质是一个功能完备、特性丰富的类,用好了能让代码变得非常优雅和健壮。
1. 基础:替换 public static final
在枚举出现之前,我们用一组 public static final 的 int 或 String 来表示常量。
java
// 旧方式
public static final int STATUS_PENDING = 0;
public static final int STATUS_APPROVED = 1;
public static final int STATUS_REJECTED = 2;
这种方式有几个致命弱点:
- 类型不安全 :任何
int值都能传入,编译器不会报错。setStatus(99)完全合法。 - 无语义:打印出来是数字,可读性差。
- 命名空间污染 :常量名必须保证唯一。
枚举优雅地解决了这些问题。
java
// 新方式
public enum OrderStatus {
PENDING,
APPROVED,
REJECTED
}
- 类型安全 :
setStatus(OrderStatus.PENDING)只能接受OrderStatus的三个实例之一。 - 语义清晰 :打印出来就是
PENDING,可读性极佳。 - 自带命名空间 :通过
OrderStatus.PENDING调用。
2. 本质:一个继承自 java.lang.Enum 的类
这是理解枚举的关键。编译器会将 enum 关键字转换成一个最终的类,这个类:
- 继承自
java.lang.Enum。因此,所有枚举都共享Enum的方法。 - 是
final的,无法被继承。 - 构造方法是
private的,确保实例只能在类内部创建,且在类加载时就被实例化。
这意味着,枚举可以拥有字段、方法、构造方法 ,甚至可以实现接口。
java
public enum Planet {
MERCURY(3.303e+23, 2.4397e6),
VENUS(4.869e+24, 6.0518e6),
EARTH(5.976e+24, 6.37814e6);
private final double mass; //以千克为单位
private final double radius; // 以米为单位
// 构造器必须是 private
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
public double mass() { return mass; }
public double radius() { return radius; }
// 可以有自己的方法
public double surfaceGravity() {
final double G = 6.67300E-11; //万有引力常数
return G * mass / (radius * radius);
}
}
思考 :这种设计让枚举从一个"常量列表"变成了"一组相关的、行为各异的、有限的对象"。比如 Planet,每个行星都是一个对象,有自己的属性(质量、半径)和行为(计算表面重力)。这比用一堆静态变量和静态方法要面向对象得多。
3. 进阶:为每个枚举常量赋予不同的行为
这是枚举最强大的特性之一:可以为每个枚举常量提供其独有的方法实现,通过声明抽象方法来实现。
java
public enum Operation {
PLUS {
@Override
public double apply(double x, double y) {
return x + y;
}
},
MINUS {
@Override
public double apply(double x, double y) {
return x - y;
}
},
TIMES {
@Override
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE {
@Override
public double apply(double x, double y) {
return x / y;
}
};
// 抽象方法,强制每个常量必须实现
public abstract double apply(double x, double y);
}
这里的 PLUS { ... } 语法,本质上相当于一个匿名内部类,它继承了 Operation 并实现了 apply 方法。
思考 :这种写法完美替代了 if-else 或 switch 语句。当需要增加新的运算时,只需添加一个新的枚举常量并实现其逻辑,而无需修改原有的 if-else 或 switch 结构。这完全符合开闭原则(对扩展开放,对修改关闭)。
4. 常用 API 与陷阱
name(): 返回枚举常量的名称,如"PLUS"。通常建议只在需要序列化或与外部系统交互时使用。ordinal(): 返回枚举常量在声明中的序号(从0开始)。强烈不建议在业务逻辑中使用它! 它的值极易因为修改枚举声明的顺序而改变,非常脆弱。如果需要关联索引,自己加一个字段。values(): 返回一个包含所有枚举常量的数组。这个方法是编译器加的,Enum类本身没有。valueOf(String): 根据名称字符串获取对应的枚举实例。如果名称不存在,会抛出IllegalArgumentException。
5. 思考与最佳实践
-
枚举是实现单例模式的最佳方式
《Effective Java》中明确指出,单元素枚举是实现单例的最佳方法。因为它不仅能防止通过反射创建新对象,还能保证在序列化和反序列化时不会创建新对象。JVM 从根本上保证了单例的唯一性。javapublic enum Singleton { INSTANCE; public void doSomething() { // ... } } -
何时使用枚举?
当你需要一组固定的、编译时确定的 常量时。例如:星期、月份、订单状态、操作系统类型等。
如果这组值是运行时动态变化的(比如从数据库或配置文件读取),那么枚举就不适用了,应该考虑Map或List。 -
枚举与
switch
switch语句天然支持枚举,并且编译器会进行检查,确保你没有遗漏任何一个case(如果没有default分支)。这是一个很好的实践。 -
不要用
==还是.equals()?
对于枚举,用==就够了。JVM 保证每个枚举常量只有一个实例,所以==和.equals()效果相同,且==更高效、更简洁。
总结
- 基础:类型安全的常量集合。
- 进阶:一个功能完备的类,可以有自己的属性、方法。
- 高阶 :可以为每个常量实现不同的行为,替代
if-else/switch。 - 最佳实践 :是实现单例模式的最佳选择,但不要滥用
ordinal()。
不要再把枚举只看作常量了。它是一个强大、安全且优雅的 Java 特性,善用它,你的代码会提升一个档次。