什么是枚举?
用一句话概括:枚举就是“固定选项的集合”。
为什么需要枚举?
- 类型安全:编译期就能发现错误,不会传入非法值
- 可读性强:
OrderStatus.PAID比"1"或"paid"清晰得多 - 防止非法值:方法参数限定了取值范围,调用方无法随意传值
基础篇:最简单的枚举
publicenumOrderStatus{CREATED,// 已创建PAID,// 已支付SHIPPED,// 已发货COMPLETED// 已完成}枚举值之间用逗号隔开,最后一个可以有分号。
使用方式:
publicclassOrder{privateOrderStatusstatus;// ✅ 类型安全publicvoidpay(){if(status==OrderStatus.CREATED){status=OrderStatus.PAID;}}}进阶篇:带字段和方法的枚举
实际项目中,枚举通常需要携带额外属性(如编码、描述等):
publicenumOrderStatus{CREATED("0","已创建"),PAID("1","已支付"),SHIPPED("2","已发货"),COMPLETED("3","已完成");// 成员变量privatefinalStringcode;// 用于数据库存储privatefinalStringdesc;// 用于前端展示// 构造方法(必须是 private)privateOrderStatus(Stringcode,Stringdesc){this.code=code;this.desc=desc;}// getter 方法publicStringgetCode(){returncode;}publicStringgetDesc(){returndesc;}}关键点:
- 构造方法必须是
private,这是枚举的语法规定 - 字段推荐用
final,枚举值应该是不可变的(后面会重点讲)
枚举的内置方法
Java 为所有枚举自动提供了几个实用方法:
// 1. name():返回枚举常量的名称OrderStatus.PAID.name();// 返回 "PAID"// 2. ordinal():返回声明顺序(从 0 开始)OrderStatus.CREATED.ordinal();// 返回 0OrderStatus.PAID.ordinal();// 返回 1// 3. valueOf(String):字符串转枚举OrderStatusstatus=OrderStatus.valueOf("PAID");// 相当于 OrderStatus.PAID// 4. values():获取所有枚举常量for(OrderStatusstatus:OrderStatus.values()){System.out.println(status);}// 输出:CREATED PAID SHIPPED COMPLETED应用场景
1. 在实体类中作为字段
publicclassOrderEntity{privateStringorderId;privateStringuserId;privateOrderStatusstatus;// ✅ 用枚举类型// 判断订单是否可支付publicbooleancanPay(){returnstatus==OrderStatus.CREATED;}}2. 作为方法的参数或返回值
publicclassOrderService{// 参数限制:只能传 OrderStatus 中定义的值publicvoidupdateStatus(StringorderId,OrderStatusnewStatus){// 业务逻辑...}}3. 配合 switch 做多分支逻辑
publicStringgetStatusDesc(OrderStatusstatus){switch(status){caseCREATED:return"订单已创建,等待支付";casePAID:return"已支付,正在准备发货";caseSHIPPED:return"已发货,请注意查收";caseCOMPLETED:return"订单已完成";default:return"未知状态";// 理论上不执行,保留健壮性}}Spring Boot 中的枚举映射(@Enumerated)
在 Spring Boot + JPA 项目中,枚举需要存到数据库,这时要用@Enumerated注解:
@Entity@Table(name="orders")publicclassOrder{@IdprivateStringorderId;privateStringuserId;// 方式一:存枚举名称(字符串)@Enumerated(EnumType.STRING)privateOrderStatusstatus;// 存 "PAID" 到数据库// 方式二:存枚举序号(数字)⚠️ 不推荐!// @Enumerated(EnumType.ORDINAL)// private OrderStatus status; // 存 0, 1, 2... 到数据库}EnumType.STRINGvsEnumType.ORDINAL
| 对比维度 | EnumType.STRING✅ | EnumType.ORDINAL❌ |
|---|---|---|
| 存什么 | 枚举名称(如"PAID") | 序号(如1) |
| 可读性 | 数据库里一眼看懂 | 需要查文档才知道1代表什么 |
| 顺序依赖 | 不依赖声明顺序 | ❌ 新增枚举插在中间会乱套 |
| 推荐 | ✅ 推荐使用 | ❌ 不推荐,有坑 |
配合 MyBatis 使用(另一种方式)
如果用 MyBatis,可以自定义 TypeHandler 或直接用code字段存:
// 存 code 到数据库order.setStatus(OrderStatus.PAID.getCode());// 存 "1"枚举 vs 结构体(对比)
初学时有个对比会清晰一些。
核心区别在于"实例数量":
| 对比维度 | 枚举 | 结构体 |
|---|---|---|
| 实例数量 | 固定有限(编译期确定) | 无限(运行时随意 new) |
| 能否伪造 | 不能(类型安全) | 可以(绕开常量定义) |
| 适用场景 | 固定选项(状态、类型等) | 数据容器(用户、商品等) |
一句话记住:枚举是"选一个",结构体是"装一堆"。
枚举必须不可变!
枚举值是全局单例的,这意味着如果你修改了某个枚举值的属性,会影响所有使用它的地方。
❌ 反面示例(在CSDN上看到的错误示例)
publicenumRole{WARRIOR("战士",100),// ❌ 没有 final 的字段MAGE("法师",80);privateStringname;privateinthp;privateRole(Stringname,inthp){this.name=name;this.hp=hp;}publicvoidsetHp(inthp){// ❌ 提供了 setter!this.hp=hp;}}// 使用方Role.MAGE.setHp(50);// ❌ 修改了枚举值本身System.out.println(Role.MAGE.getHp());// 输出 50,不是 80 了!问题:所有法师的血量都被改成了 50!
✅ 正确写法
publicenumRole{WARRIOR("战士",100),MAGE("法师",80);privatefinalStringname;// ✅ finalprivatefinalinthp;// ✅ finalprivateRole(Stringname,inthp){this.name=name;this.hp=hp;}publicStringgetName(){returnname;}publicintgetHp(){returnhp;}// ✅ 没有 setter!}如果每个玩家的血量要独立变化怎么办?
血量应该存在玩家对象里,而不是枚举里:
publicclassPlayer{privateRolerole;// 职业(模板)privateintcurrentHp;// 当前血量(实例状态)privateintcurrentAttack;// 当前攻击力privateintcurrentDefense;// 当前防御力publicPlayer(Rolerole){this.role=role;this.currentHp=role.getHp();// 从模板复制this.currentAttack=role.getAttack();this.currentDefense=role.getDefense();}publicvoidattack(Playertarget){intdamage=this.currentAttack-target.currentDefense;if(damage>0){target.currentHp-=damage;// ✅ 只修改玩家的血量}}}总结
| 核心要点 | 说明 |
|---|---|
| 枚举是什么 | 固定选项的集合,用于表示有限的、不变的值 |
| 为什么用枚举 | 类型安全、可读性强、防止非法值 |
| 基本语法 | public enum Xxx { A, B, C } |
| 带字段写法 | 私有构造方法 +final字段 + getter |
| Spring Boot 映射 | 使用@Enumerated(EnumType.STRING)存到数据库 |
| 与结构体区别 | 枚举实例数固定,结构体可无限创建 |
| 最重要原则 | 枚举必须不可变,所有字段都应该是final的 |