Java 枚举类型:3个经典应用场景与实战案例
 
枚举( enum )是 Java 中一种特殊的类,它通过固定的常量集合来表示有限且离散的状态,不仅能提升代码可读性,还能避免魔法值、减少错误,是后端开发中非常实用的工具。
 
 
场景1:状态/类型定义


这是枚举最基础也最普遍的用法,当系统中存在固定的状态集合(比如订单状态、用户角色、支付方式)时,用枚举代替零散的字符串/数字常量,能让代码更清晰、更安全。
 
问题背景
 
如果不用枚举,可能会这样定义订单状态:
 
java   
public class OrderService {
    // 魔法值,容易写错、维护困难
    public static final int ORDER_UNPAID = 0;
    public static final int ORDER_PAID = 1;
    public static final int ORDER_DELIVERED = 2;
    public static final int ORDER_COMPLETED = 3;
    public static final int ORDER_CANCELLED = 4;

    public String getOrderStatusDesc(int status) {
        if (status == ORDER_UNPAID) {
            return "待付款";
        } else if (status == ORDER_PAID) {
            return "已付款";
        } else if (status == ORDER_DELIVERED) {
            return "已发货";
        } else if (status == ORDER_COMPLETED) {
            return "已完成";
        } else if (status == ORDER_CANCELLED) {
            return "已取消";
        } else {
            return "未知状态";
        }
    }
}
 
 
这种写法的问题很明显:
 
1. 魔法值硬编码,修改时容易漏改;
2. 类型不安全,传入  999  这种不存在的状态也不会报错;
3. 状态和描述的映射关系分散,可读性差。
 
枚举实现方案
 
用枚举封装所有状态,直接把状态码、状态描述都封装到枚举里:
 
java   
/**
 * 订单状态枚举
 */
public enum OrderStatusEnum {
    UNPAID(0, "待付款"),
    PAID(1, "已付款"),
    DELIVERED(2, "已发货"),
    COMPLETED(3, "已完成"),
    CANCELLED(4, "已取消");

    // 状态码
    private final Integer code;
    // 状态描述
    private final String desc;

    OrderStatusEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    // 根据状态码获取枚举
    public static OrderStatusEnum getByCode(Integer code) {
        if (code == null) {
            return null;
        }
        for (OrderStatusEnum status : OrderStatusEnum.values()) {
            if (status.getCode().equals(code)) {
                return status;
            }
        }
        return null;
    }

    // Getter
    public Integer getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}
 
 
业务中使用
 
java   
public class OrderService {
    public String getOrderStatusDesc(Integer statusCode) {
        OrderStatusEnum statusEnum = OrderStatusEnum.getByCode(statusCode);
        return statusEnum != null ? statusEnum.getDesc() : "未知状态";
    }

    // 示例:更新订单状态
    public void payOrder(Long orderId) {
        // 直接用枚举常量,不会写错
        updateOrderStatus(orderId, OrderStatusEnum.PAID);
    }

    private void updateOrderStatus(Long orderId, OrderStatusEnum status) {
        // 数据库操作,把状态码存进去
        System.out.println("更新订单" + orderId + "状态为:" + status.getDesc());
    }
}

 



 
场景2:策略模式(替换大量 if/else)
 
当有大量分支逻辑,比如不同类型的商品计算优惠、不同渠道发送消息时,用枚举配合策略模式,可以彻底消灭臃肿的  if/else  或  switch ,让代码更符合开闭原则。
 
问题背景
 
假设有一个商品优惠计算的需求,不同商品类型有不同的优惠规则:
 
- 普通商品:无优惠
- 会员商品:打9折
- 促销商品:满100减20
- 秒杀商品:固定8折
 
如果不用枚举,代码会变成这样:
 
java   
public class DiscountService {
    public BigDecimal calculateDiscount(String type, BigDecimal price) {
        if ("NORMAL".equals(type)) {
            return price;
        } else if ("MEMBER".equals(type)) {
            return price.multiply(new BigDecimal("0.9"));
        } else if ("PROMOTION".equals(type)) {
            if (price.compareTo(new BigDecimal("100")) >= 0) {
                return price.subtract(new BigDecimal("20"));
            }
            return price;
        } else if ("SECKILL".equals(type)) {
            return price.multiply(new BigDecimal("0.8"));
        } else {
            throw new IllegalArgumentException("不支持的商品类型");
        }
    }
}
 
 
这种写法的缺点:
 
1. 新增优惠类型时,必须修改这个方法,违反开闭原则;
2. 分支越多,代码越臃肿,可读性极差;
3. 类型是字符串,容易写错,编译器无法校验。
 
枚举+策略模式实现
 
在枚举中定义抽象方法,每个枚举常量实现自己的优惠逻辑,相当于每个枚举都是一个独立的策略:
 
java   
import java.math.BigDecimal;

/**
 * 商品优惠策略枚举
 */
public enum DiscountStrategyEnum {
    NORMAL("普通商品") {
        @Override
        public BigDecimal calculate(BigDecimal price) {
            // 无优惠
            return price;
        }
    },
    MEMBER("会员商品") {
        @Override
        public BigDecimal calculate(BigDecimal price) {
            // 9折
            return price.multiply(new BigDecimal("0.9"));
        }
    },
    PROMOTION("促销商品") {
        @Override
        public BigDecimal calculate(BigDecimal price) {
            // 满100减20
            if (price.compareTo(new BigDecimal("100")) >= 0) {
                return price.subtract(new BigDecimal("20"));
            }
            return price;
        }
    },
    SECKILL("秒杀商品") {
        @Override
        public BigDecimal calculate(BigDecimal price) {
            // 固定8折
            return price.multiply(new BigDecimal("0.8"));
        }
    };

    private final String desc;

    DiscountStrategyEnum(String desc) {
        this.desc = desc;
    }

    /**
     * 抽象优惠计算方法,每个枚举实现自己的逻辑
     * @param price 原价
     * @return 优惠后价格
     */
    public abstract BigDecimal calculate(BigDecimal price);

    public String getDesc() {
        return desc;
    }
}
 
 
业务中使用
 
java   
public class DiscountService {
    public BigDecimal calculateDiscount(DiscountStrategyEnum type, BigDecimal price) {
        // 直接调用枚举的策略方法,无任何分支判断
        return type.calculate(price);
    }

    public static void main(String[] args) {
        DiscountService service = new DiscountService();
        BigDecimal price = new BigDecimal("200");

        System.out.println("普通商品优惠后:" + service.calculateDiscount(DiscountStrategyEnum.NORMAL, price));
        System.out.println("会员商品优惠后:" + service.calculateDiscount(DiscountStrategyEnum.MEMBER, price));
        System.out.println("促销商品优惠后:" + service.calculateDiscount(DiscountStrategyEnum.PROMOTION, price));
        System.out.println("秒杀商品优惠后:" + service.calculateDiscount(DiscountStrategyEnum.SECKILL, price));
    }
}
 
 
这种写法的优势:
 
- 新增优惠类型时,只需要新增一个枚举常量并实现  calculate  方法,不需要修改原有代码;
- 分支逻辑被封装在枚举内部,结构清晰,可读性强;
- 类型安全,传入不存在的枚举会直接编译报错。
 
 



 
场景3:统一返回码(后端接口必备)
 
后端接口开发中,统一的响应码和响应描述是规范接口的基础。用枚举封装返回码,可以避免不同接口返回的码值混乱,也方便前后端联调时统一约定。
 
问题背景
 
如果不用枚举,很多人会在每个接口中硬编码返回码:
 
java   
public class UserController {
    // 登录接口
    public Result login(String username, String password) {
        if (username == null || password == null) {
            // 参数错误,返回400
            return new Result(400, "参数不能为空", null);
        }
        User user = userService.login(username, password);
        if (user == null) {
            // 登录失败,返回500?还是401?
            return new Result(500, "用户名或密码错误", null);
        }
        return new Result(200, "操作成功", user);
    }

    // 注册接口
    public Result register(User user) {
        if (userService.exists(user.getUsername())) {
            // 用户名已存在,返回什么码?400还是409?
            return new Result(400, "用户名已存在", null);
        }
        // ...
        return new Result(200, "注册成功", null);
    }
}
 
 
这种写法的问题:
 
- 码值没有统一规范,比如登录失败有人用401,有人用500,前后端联调时非常混乱;
- 响应描述不统一,同样的错误可能有多种描述;
- 没有集中管理,修改时需要全局搜索替换,容易遗漏。
 
枚举实现方案
 
定义一个全局响应码枚举,把所有接口可能用到的返回码都统一管理起来:
 
java   
/**
 * 全局响应码枚举
 */
public enum ResultCodeEnum {
    // 成功
    SUCCESS(200, "操作成功"),
    // 客户端错误
    BAD_REQUEST(400, "请求参数错误"),
    UNAUTHORIZED(401, "未授权,请重新登录"),
    FORBIDDEN(403, "没有访问权限"),
    NOT_FOUND(404, "资源不存在"),
    // 服务端错误
    INTERNAL_SERVER_ERROR(500, "服务器内部错误"),
    // 业务自定义错误
    USER_NOT_EXIST(10001, "用户不存在"),
    USERNAME_PASSWORD_ERROR(10002, "用户名或密码错误"),
    USERNAME_ALREADY_EXIST(10003, "用户名已存在"),
    PHONE_ALREADY_EXIST(10004, "手机号已被注册"),
    TOKEN_EXPIRED(10005, "登录已过期,请重新登录");

    // 响应码
    private final Integer code;
    // 响应描述
    private final String message;

    ResultCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}
 
 
配合统一响应类使用
 
java   
/**
 * 统一响应结果封装
 */
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    // 私有构造,只能通过静态方法创建
    private Result() {}

    // 成功响应(带数据)
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(ResultCodeEnum.SUCCESS.getCode());
        result.setMessage(ResultCodeEnum.SUCCESS.getMessage());
        result.setData(data);
        return result;
    }

    // 成功响应(无数据)
    public static <T> Result<T> success() {
        return success(null);
    }

    // 失败响应(用枚举)
    public static <T> Result<T> fail(ResultCodeEnum resultCode) {
        Result<T> result = new Result<>();
        result.setCode(resultCode.getCode());
        result.setMessage(resultCode.getMessage());
        return result;
    }

    // 失败响应(自定义消息)
    public static <T> Result<T> fail(ResultCodeEnum resultCode, String message) {
        Result<T> result = new Result<>();
        result.setCode(resultCode.getCode());
        result.setMessage(message);
        return result;
    }

    // Getter & Setter
    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
 
 
业务中使用
 
java   
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public Result<User> login(String username, String password) {
        // 参数校验
        if (username == null || password == null) {
            return Result.fail(ResultCodeEnum.BAD_REQUEST);
        }
        User user = userService.login(username, password);
        if (user == null) {
            // 直接用枚举,前后端都知道10002对应的错误是什么
            return Result.fail(ResultCodeEnum.USERNAME_PASSWORD_ERROR);
        }
        return Result.success(user);
    }

    @PostMapping("/register")
    public Result<Void> register(User user) {
        if (userService.existsByUsername(user.getUsername())) {
            return Result.fail(ResultCodeEnum.USERNAME_ALREADY_EXIST);
        }
        if (userService.existsByPhone(user.getPhone())) {
            return Result.fail(ResultCodeEnum.PHONE_ALREADY_EXIST);
        }
        userService.register(user);
        return Result.success();
    }
}
 
枚举的额外小技巧
 
1. 实现接口:枚举可以实现接口,比如上面的策略模式,也可以定义一个  BaseEnum  接口,所有业务枚举都实现它,方便统一处理。
 java   
public interface BaseEnum {
    Integer getCode();
    String getDesc();
}
2. 序列化/反序列化:在和数据库交互时,可以用  @EnumValue (MyBatis)或  @JsonValue (Jackson)指定枚举和数据库字段的映射。
3. 单例实现:枚举是天然的单例,《Effective Java》中也推荐用枚举实现单例,线程安全且能防止反射破坏。
 
 
总结
 
枚举不是简单的常量集合,它本质上是一种特殊的类,能封装数据和行为。在实际开发中,这3个场景几乎是每个后端程序员都会用到的:
状态/类型定义:让离散的状态更规范、更安全;
策略模式:消灭臃肿的  if/else ,让代码更优雅;
统一返回码:规范接口响应,提升前后端联调效率。

 

更多推荐