Enum ------ 枚举类型的底层实现
适用版本: JDK 8 难度等级: 基础 核心概念: 枚举的本质是 final class、ordinal 与 name、EnumSet/EnumMap
一、枚举的本质
Java 中 enum 关键字定义的枚举类型,编译后实际上是一个继承自 java.lang.Enum 的 final 类。
java
public enum Color { RED, GREEN, BLUE }
// 编译后大致等价于:
// public final class Color extends Enum<Color> {
// public static final Color RED = new Color("RED", 0);
// public static final Color GREEN = new Color("GREEN", 1);
// public static final Color BLUE = new Color("BLUE", 2);
// private static final Color[] $VALUES = {RED, GREEN, BLUE};
// public static Color[] values() { return $VALUES.clone(); }
// public static Color valueOf(String name) { return Enum.valueOf(Color.class, name); }
// private Color(String name, int ordinal) { super(name, ordinal); }
// }
反编译验证
bash
# 编译 Color.java
javac Color.java
# 反编译查看
javap -c -p Color.class
二、Enum 基类源码
java
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name; // 枚举常量的名称
private final int ordinal; // 枚举常量的序号(从0开始)
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public final String name() { return name; }
public final int ordinal() { return ordinal; }
public String toString() { return name; }
public final boolean equals(Object other) {
return this == other; // 枚举常量是单例,== 足够
}
public final int hashCode() {
return super.hashCode(); // 使用 Object 的 hashCode
}
// 禁止克隆------保持单例
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public final int compareTo(E o) {
Enum<?> other = (Enum<?>) o;
Enum<E> self = this;
if (self.getClass() != other.getClass()
&& self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal; // 按声明顺序比较
}
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>) clazz : (Class<E>) zuper;
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null) return result;
if (name == null) throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
// 禁止 finalize
protected final void finalize() {}
}
三、枚举的高级特性
3.1 带属性和方法的枚举
java
public enum HttpStatus {
OK(200, "请求成功"),
NOT_FOUND(404, "资源未找到"),
INTERNAL_ERROR(500, "服务器内部错误");
private final int code;
private final String description;
HttpStatus(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() { return code; }
public String getDescription(){ return description; }
public static HttpStatus fromCode(int code) {
for (HttpStatus status : values()) {
if (status.code == code) return status;
}
throw new IllegalArgumentException("未知状态码: " + code);
}
}
java
public class EnumFieldsDemo {
public static void main(String[] args) {
HttpStatus status = HttpStatus.NOT_FOUND;
System.out.println(status.getCode() + " → " + status.getDescription());
HttpStatus resolved = HttpStatus.fromCode(200);
System.out.println(resolved); // OK
}
}
3.2 枚举实现接口
java
public enum Calculator {
ADD {
@Override
public double apply(double a, double b) { return a + b; }
},
SUBTRACT {
@Override
public double apply(double a, double b) { return a - b; }
},
MULTIPLY {
@Override
public double apply(double a, double b) { return a * b; }
},
DIVIDE {
@Override
public double apply(double a, double b) {
if (b == 0) throw new ArithmeticException("除零");
return a / b;
}
};
public abstract double apply(double a, double b);
public static void main(String[] args) {
System.out.println(ADD.apply(10, 5)); // 15.0
System.out.println(MULTIPLY.apply(10, 5)); // 50.0
}
}
四、EnumSet 与 EnumMap
两个专门为枚举设计的高效集合类:
java
import java.util.EnumSet;
import java.util.EnumMap;
public class EnumCollectionsDemo {
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }
public static void main(String[] args) {
// EnumSet ------ 内部用位向量实现,极高效
EnumSet<Day> workdays = EnumSet.range(Day.MON, Day.FRI);
EnumSet<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
System.out.println("工作日: " + workdays);
System.out.println("周末: " + weekend);
// EnumMap ------ key 必须是枚举,内部用数组索引
EnumMap<Day, String> schedule = new EnumMap<>(Day.class);
schedule.put(Day.MON, "开会");
schedule.put(Day.FRI, "汇报");
System.out.println("周一安排: " + schedule.get(Day.MON));
}
}
五、综合实战:策略模式的枚举实现
java
/**
* 使用枚举实现策略模式------免去大量 if-else
*/
public class DiscountStrategy {
public enum DiscountType {
NORMAL {
@Override
double apply(double price) { return price; }
},
VIP {
@Override
double apply(double price) { return price * 0.85; }
},
SVIP {
@Override
double apply(double price) { return price * 0.70; }
},
EMPLOYEE {
@Override
double apply(double price) { return price * 0.50; }
};
abstract double apply(double price);
}
public static double calculate(String type, double price) {
DiscountType dt = DiscountType.valueOf(type.toUpperCase());
return dt.apply(price);
}
public static void main(String[] args) {
System.out.println("普通价格: " + calculate("normal", 1000));
System.out.println("VIP价格: " + calculate("vip", 1000));
System.out.println("SVIP价格: " + calculate("svip", 1000));
System.out.println("员工价格: " + calculate("employee", 1000));
}
}
六、面试要点
| 问题 | 关键要点 |
|---|---|
| enum 的本质 | 继承 Enum 的 final 类,编译期生成 |
| ordinal 的用途和风险 | 表示声明顺序,修改顺序会导致逻辑错误 |
| 为什么枚举能防止反射破坏单例 | 反射 newInstance 时会检查是否是枚举类,若是则抛异常 |
| 为什么枚举不能 clone | Enum 的 clone 直接抛 CloneNotSupportedException |
| EnumMap/EnumSet 的高效原理 | 使用数组/位向量,O(1) 操作 |