Java中的枚举本质上是一种特殊的类,除了默认提供的枚举实例外,还可以像普通类一样添加自定义属性、方法和构造器,从而让枚举具备更丰富的业务功能,满足更多开发场景的需求。

Java枚举的基本结构
普通枚举只定义枚举实例,比如定义一个表示季节的简单枚举:
public enum SeasonEnum {
SPRING, SUMMER, AUTUMN, WINTER
}
这种基础枚举只能做实例匹配,无法携带额外信息或实现自定义逻辑,而通过添加属性、方法和构造器就可以突破这个限制。
在枚举中添加属性
枚举可以添加成员变量作为属性,通常这些属性会和枚举实例的业务含义绑定。属性的定义和普通类的成员变量一致,可以设置访问修饰符。
比如我们需要给季节枚举添加每个季节的月份范围和描述信息,就可以定义两个属性:
public enum SeasonEnum {
// 枚举实例,需要传入构造器对应的参数
SPRING("3-5月", "春暖花开"),
SUMMER("6-8月", "夏日炎炎"),
AUTUMN("9-11月", "秋高气爽"),
WINTER("12-2月", "寒冬腊月");
// 自定义属性
private String monthRange;
private String description;
// 构造器,默认是private修饰,不能声明为public或protected
private SeasonEnum(String monthRange, String description) {
this.monthRange = monthRange;
this.description = description;
}
// 属性的getter方法
public String getMonthRange() {
return monthRange;
}
public String getDescription() {
return description;
}
}
这里需要注意,枚举的构造器默认是private修饰的,即使不写private关键字,编译器也会自动添加,不允许声明为public或protected,因为枚举实例的创建只能在枚举内部完成。
在枚举中添加自定义方法
枚举除了可以添加属性的getter方法,还可以定义普通方法、抽象方法,甚至可以重写Object类的方法。
普通自定义方法
比如给季节枚举添加一个判断某个月份是否属于当前季节的方法:
public enum SeasonEnum {
SPRING("3-5月", "春暖花开") {
@Override
public boolean isInSeason(int month) {
return month >= 3 && month <= 5;
}
},
SUMMER("6-8月", "夏日炎炎") {
@Override
public boolean isInSeason(int month) {
return month >= 6 && month <= 8;
}
},
AUTUMN("9-11月", "秋高气爽") {
@Override
public boolean isInSeason(int month) {
return month >= 9 && month <= 11;
}
},
WINTER("12-2月", "寒冬腊月") {
@Override
public boolean isInSeason(int month) {
return month == 12 || month <= 2;
}
};
private String monthRange;
private String description;
private SeasonEnum(String monthRange, String description) {
this.monthRange = monthRange;
this.description = description;
}
public String getMonthRange() {
return monthRange;
}
public String getDescription() {
return description;
}
// 抽象方法,每个枚举实例必须实现
public abstract boolean isInSeason(int month);
}
重写Object类方法
枚举默认重写了toString方法,返回枚举实例的名称,我们也可以自定义重写,比如返回更友好的描述:
public enum SeasonEnum {
SPRING("3-5月", "春暖花开"),
SUMMER("6-8月", "夏日炎炎"),
AUTUMN("9-11月", "秋高气爽"),
WINTER("12-2月", "寒冬腊月");
private String monthRange;
private String description;
private SeasonEnum(String monthRange, String description) {
this.monthRange = monthRange;
this.description = description;
}
public String getMonthRange() {
return monthRange;
}
public String getDescription() {
return description;
}
// 重写toString方法
@Override
public String toString() {
return this.name() + ":" + description + ",月份范围:" + monthRange;
}
}
枚举构造器的使用规则
枚举构造器的使用有几个需要特别注意的点:
- 枚举构造器只能是
private修饰,不允许其他访问修饰符,这是枚举的设计特性,保证枚举实例的唯一性和可控性。 - 枚举实例的定义必须放在枚举类的最前面,构造器定义在枚举实例之后,否则会编译报错。
- 如果枚举没有显式定义构造器,编译器会自动生成一个无参的
private构造器。 - 枚举构造器不能抛出受检异常,除非是所有枚举实例的构造都符合异常处理规则。
实际应用示例
下面是一个更贴近实际开发的示例,定义一个表示订单状态的枚举,包含状态编码、描述,以及状态流转的校验方法:
public enum OrderStatusEnum {
// 枚举实例,传入编码和描述
WAIT_PAY(1, "待支付") {
@Override
public boolean canTransferTo(OrderStatusEnum targetStatus) {
return targetStatus == PAID || targetStatus == CANCELED;
}
},
PAID(2, "已支付") {
@Override
public boolean canTransferTo(OrderStatusEnum targetStatus) {
return targetStatus == SHIPPED || targetStatus == REFUNDING;
}
},
SHIPPED(3, "已发货") {
@Override
public boolean canTransferTo(OrderStatusEnum targetStatus) {
return targetStatus == FINISHED || targetStatus == REFUNDING;
}
},
FINISHED(4, "已完成") {
@Override
public boolean canTransferTo(OrderStatusEnum targetStatus) {
return false;
}
},
CANCELED(5, "已取消") {
@Override
public boolean canTransferTo(OrderStatusEnum targetStatus) {
return false;
}
},
REFUNDING(6, "退款中") {
@Override
public boolean canTransferTo(OrderStatusEnum targetStatus) {
return targetStatus == CANCELED || targetStatus == PAID;
}
};
// 状态编码
private int code;
// 状态描述
private String desc;
// 私有构造器
private OrderStatusEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
// 编码getter
public int getCode() {
return code;
}
// 描述getter
public String getDesc() {
return desc;
}
// 抽象方法:判断当前状态能否流转到目标状态
public abstract boolean canTransferTo(OrderStatusEnum targetStatus);
// 根据编码获取枚举实例的静态方法
public static OrderStatusEnum getByCode(int code) {
for (OrderStatusEnum status : OrderStatusEnum.values()) {
if (status.code == code) {
return status;
}
}
return null;
}
}
使用这个枚举时,就可以很方便地做状态校验:
public class OrderService {
public void updateOrderStatus(OrderStatusEnum currentStatus, OrderStatusEnum targetStatus) {
if (currentStatus.canTransferTo(targetStatus)) {
System.out.println("状态流转合法,执行更新操作");
} else {
System.out.println("当前状态" + currentStatus.getDesc() + "无法流转到" + targetStatus.getDesc());
}
}
public static void main(String[] args) {
OrderService service = new OrderService();
// 测试状态流转
service.updateOrderStatus(OrderStatusEnum.WAIT_PAY, OrderStatusEnum.PAID);
service.updateOrderStatus(OrderStatusEnum.WAIT_PAY, OrderStatusEnum.SHIPPED);
}
}
注意事项
在使用枚举扩展功能时,还要注意几个问题:
- 枚举的属性如果是可变对象,需要考虑线程安全问题,尽量将属性定义为
final,避免被修改。 - 枚举的
values()方法会返回所有枚举实例的数组,每次调用都会创建新数组,频繁调用可以考虑缓存结果。 - 不要给枚举添加过于复杂的业务逻辑,枚举的核心作用还是定义固定的实例集合,复杂逻辑可以放在其他业务类中实现。