枚举
概述
枚举(enum)是 Java 中表示固定一组常量的特殊类型。它既是一种数据类型,也是一种特殊的类:由 JVM 保证每个枚举常量全局唯一、线程安全,且自带 name、ordinal 等能力。相比用 public static final int 或字符串常量表示状态/类型,枚举更类型安全、可读性更好,且能携带字段和方法,适合表达「有限的几种取值」(如星期、季节、订单状态、权限级别等)。
通俗理解:枚举就是「只能取预定几个值」的类型;每个值是一个枚举常量,本质是该枚举类的单例实例。学习枚举有助于写出更清晰、更少魔法数字的代码,并为后续学习 注解 和 反射 打基础。
为什么需要枚举
- 类型安全:用枚举变量只能赋枚举常量,编译期即可发现错误;用
int或String容易传入非法值。 - 可读性:
DayOfWeek.MONDAY比数字1或字符串"MONDAY"更直观。 - 单例与唯一性:每个枚举常量在 JVM 中唯一,天然适合做单例或固定集合。
- 可扩展:枚举可以有成员变量、构造方法、普通方法,甚至实现接口,而不只是「名字 + 序号」。
基本语法
简单枚举
使用 enum 关键字定义,枚举常量用逗号分隔,末尾分号可选(无其他成员时可省略)。
java
// 最简单的枚举:仅列举常量名
public enum DayOfWeek {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
// 使用
DayOfWeek today = DayOfWeek.FRIDAY;
System.out.println(today); // FRIDAY
System.out.println(today.name()); // FRIDAY(枚举常量名)
System.out.println(today.ordinal()); // 4(声明顺序,从 0 开始)提示
枚举常量命名习惯使用全大写、多个单词用下划线(如 MAX_PRIORITY),与常量命名一致。
枚举是类
枚举编译后继承 java.lang.Enum,因此不能再用 extends 继承其他类(Java 单继承);但可以实现接口。每个枚举常量本质上是该枚举类的一个实例,由 JVM 在类加载时创建,且全局唯一。
java
public enum Size {
SMALL, MEDIUM, LARGE
}
// 等价于(概念上):每个常量都是 Size 的实例
// Size SMALL = new Size("SMALL", 0);
// Size MEDIUM = new Size("MEDIUM", 1);
// Size LARGE = new Size("LARGE", 2);带字段与方法的枚举
枚举可以有构造方法、成员变量和普通方法。构造方法必须是 包私有或 private,不能从外部 new 枚举实例;常量声明时相当于调用该构造方法。
示例 1:为每个常量绑定描述
java
public enum Season {
SPRING("春天", "温暖"),
SUMMER("夏天", "炎热"),
AUTUMN("秋天", "凉爽"),
WINTER("冬天", "寒冷");
private final String nameCn;
private final String desc;
// 枚举的构造方法:不能是 public,由常量列表隐式调用
Season(String nameCn, String desc) {
this.nameCn = nameCn;
this.desc = desc;
}
public String getNameCn() {
return nameCn;
}
public String getDesc() {
return desc;
}
}
// 使用
Season s = Season.SUMMER;
System.out.println(s.getNameCn() + " " + s.getDesc()); // 夏天 炎热示例 2:枚举中定义抽象方法(每个常量不同行为)
每个枚举常量可以重写枚举类中的抽象方法,实现「常量即策略」。
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;
}
},
MULTIPLY {
@Override
public double apply(double x, double y) {
return x * y;
}
};
// 枚举内的抽象方法:每个常量都必须提供实现
public abstract double apply(double x, double y);
}
// 使用
double r = Operation.PLUS.apply(2, 3); // 5.0
double r2 = Operation.MULTIPLY.apply(2, 3); // 6.0常用 API 与 switch
name、ordinal、valueOf、values
name():枚举常量的名称(与声明时一致)。ordinal():声明顺序(从 0 开始);不建议用 ordinal 表达业务含义,仅作内部顺序。valueOf(String name):根据名称得到枚举常量,名称不存在时抛出IllegalArgumentException。values():返回该枚举所有常量的数组(静态方法,由编译器生成)。
java
DayOfWeek[] all = DayOfWeek.values();
for (DayOfWeek d : all) {
System.out.println(d.name() + " " + d.ordinal());
}
DayOfWeek d = DayOfWeek.valueOf("MONDAY"); // 名称必须完全匹配在 switch 中使用
枚举与 switch 配合非常自然,且能利用编译期检查避免遗漏分支。
java
public static int getWorkHours(DayOfWeek day) {
switch (day) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
return 8;
case SATURDAY:
case SUNDAY:
return 0;
default:
throw new IllegalArgumentException("Unknown: " + day);
}
}注意
使用枚举的 ordinal() 表示业务含义(如数据库存 0/1/2)容易在调整常量顺序时出错,建议用自定义字段(如 int code)或 name() 持久化。
实现接口
枚举可以实现一个或多个接口,让枚举类型具备统一的行为契约。
java
public interface Describable {
String getDescription();
}
public enum Color implements Describable {
RED("红色"),
GREEN("绿色"),
BLUE("蓝色");
private final String desc;
Color(String desc) {
this.desc = desc;
}
@Override
public String getDescription() {
return desc;
}
}
// 接口引用指向枚举常量
Describable d = Color.RED;
System.out.println(d.getDescription()); // 红色注意事项
- 构造方法:枚举的构造方法不能是
public,只能是包私有或private,且不能手动new枚举实例。 - 常量顺序:枚举常量必须写在类体最前面,若有字段或方法,常量列表后要有分号。
- ordinal 的陷阱:不要依赖
ordinal()做业务编码;新增或调整常量顺序会导致值变化,应使用显式字段(如int code)或name()。 - 单例:每个枚举常量在 JVM 中唯一,反序列化也不会产生新实例,因此枚举常被用来实现线程安全的单例(如 Effective Java 推荐方式)。
- 比较:枚举常量比较应使用
==(同一实例),不必用equals;若需按名称比较,可用name().equals(...)或valueOf。
相关链接
- 类与对象 — 枚举本质是特殊类
- 接口 — 枚举可实现接口
- 注解 — 注解中常用枚举属性
- Oracle Java 教程 - 枚举