Java 枚举类(Enum)指南

50 阅读10分钟

📊 Java 枚举类(Enum)完全指南

📖 枚举的基本概念

什么是枚举?

枚举是一种特殊的类,用于定义固定数量的常量

为什么需要枚举?

// ❌ 传统方式:使用常量(问题多)
public class OldWay {
    public static final int MONDAY = 1;
    public static final int TUESDAY = 2;
    // ... 容易出错,类型不安全
    
    public void setDay(int day) {
        // 可能传入非法值:100, -1
    }
}

// ✅ 枚举方式(类型安全)
public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, 
    FRIDAY, SATURDAY, SUNDAY
}

🎯 枚举的基本语法

1. 最简单的枚举

// 定义枚举
enum Color {
    RED,    // 枚举常量
    GREEN,  // 枚举常量
    BLUE    // 枚举常量
}

// 使用枚举
public class EnumBasic {
    public static void main(String[] args) {
        // 1. 声明枚举变量
        Color myColor = Color.RED;
        
        // 2. 比较枚举
        if (myColor == Color.RED) {
            System.out.println("颜色是红色");
        }
        
        // 3. switch 语句(完美配合)
        switch (myColor) {
            case RED:
                System.out.println("红色");
                break;
            case GREEN:
                System.out.println("绿色");
                break;
            case BLUE:
                System.out.println("蓝色");
                break;
        }
        
        // 4. 遍历所有枚举值
        System.out.println("\n所有颜色:");
        for (Color color : Color.values()) {
            System.out.println(color);
        }
        
        // 5. 获取枚举名称和序号
        System.out.println("\n枚举详细信息:");
        System.out.println("名称: " + myColor.name());
        System.out.println("序号: " + myColor.ordinal());
        System.out.println("字符串转枚举: " + Color.valueOf("RED"));
    }
}

2. 带属性的枚举

// 枚举可以有自己的属性和方法
enum Planet {
    // 枚举常量(调用构造器)
    MERCURY("水星", 2439.7, 3.7),
    VENUS("金星", 6051.8, 8.87),
    EARTH("地球", 6371.0, 9.8),
    MARS("火星", 3389.5, 3.7),
    JUPITER("木星", 69911.0, 24.8),
    SATURN("土星", 58232.0, 10.4),
    URANUS("天王星", 25362.0, 8.7),
    NEPTUNE("海王星", 24622.0, 11.2);
    
    // 枚举属性
    private final String chineseName;
    private final double radius;     // 半径(km)
    private final double gravity;    // 重力(m/s²)
    
    // 枚举构造器(必须是private,可省略)
    private Planet(String chineseName, double radius, double gravity) {
        this.chineseName = chineseName;
        this.radius = radius;
        this.gravity = gravity;
    }
    
    // 枚举方法
    public double getSurfaceArea() {
        return 4 * Math.PI * radius * radius;
    }
    
    public double getWeight(double massOnEarth) {
        return massOnEarth * gravity / 9.8;
    }
    
    // Getter 方法
    public String getChineseName() {
        return chineseName;
    }
    
    public double getRadius() {
        return radius;
    }
    
    public double getGravity() {
        return gravity;
    }
    
    // 查找方法
    public static Planet findByChineseName(String name) {
        for (Planet planet : values()) {
            if (planet.chineseName.equals(name)) {
                return planet;
            }
        }
        return null;
    }
}

public class PlanetDemo {
    public static void main(String[] args) {
        // 使用枚举
        Planet earth = Planet.EARTH;
        
        System.out.println("=== 行星信息 ===");
        System.out.println("名称: " + earth.getChineseName());
        System.out.println("半径: " + earth.getRadius() + " km");
        System.out.println("表面积: " + earth.getSurfaceArea() + " km²");
        System.out.println("重力: " + earth.getGravity() + " m/s²");
        
        // 计算在地球上的重量
        double myMass = 70; // 70kg
        System.out.println("\n在不同行星上的重量:");
        for (Planet planet : Planet.values()) {
            double weight = planet.getWeight(myMass);
            System.out.printf("%s: %.1f kg%n", planet.getChineseName(), weight);
        }
        
        // 查找行星
        Planet found = Planet.findByChineseName("火星");
        System.out.println("\n查找到的行星: " + found);
    }
}

3. 带方法的枚举

// 枚举可以实现接口,定义抽象方法
enum Operation {
    // 每个枚举常量实现抽象方法
    ADD {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
        
        @Override
        public String getSymbol() {
            return "+";
        }
    },
    SUBTRACT {
        @Override
        public double apply(double x, double y) {
            return x - y;
        }
        
        @Override
        public String getSymbol() {
            return "-";
        }
    },
    MULTIPLY {
        @Override
        public double apply(double x, double y) {
            return x * y;
        }
        
        @Override
        public String getSymbol() {
            return "*";
        }
    },
    DIVIDE {
        @Override
        public double apply(double x, double y) {
            if (y == 0) {
                throw new ArithmeticException("除数不能为零");
            }
            return x / y;
        }
        
        @Override
        public String getSymbol() {
            return "/";
        }
    };
    
    // 抽象方法(每个枚举常量必须实现)
    public abstract double apply(double x, double y);
    public abstract String getSymbol();
    
    // 通用方法
    public void calculateAndPrint(double x, double y) {
        try {
            double result = apply(x, y);
            System.out.printf("%.2f %s %.2f = %.2f%n", x, getSymbol(), y, result);
        } catch (ArithmeticException e) {
            System.out.println("计算错误: " + e.getMessage());
        }
    }
}

public class OperationDemo {
    public static void main(String[] args) {
        System.out.println("=== 计算器 ===");
        
        double a = 10.5;
        double b = 2.5;
        
        // 使用枚举进行计算
        for (Operation op : Operation.values()) {
            op.calculateAndPrint(a, b);
        }
        
        // 处理除零错误
        System.out.println("\n测试除零:");
        Operation.DIVIDE.calculateAndPrint(10, 0);
        
        // 实际应用:命令模式
        System.out.println("\n实际应用:");
        Operation selected = Operation.MULTIPLY;
        selected.calculateAndPrint(8, 7);
    }
}

🏗️ 枚举的高级特性

1. 枚举实现接口

// 定义接口
interface Describeable {
    String getDescription();
    String getCategory();
}

// 枚举实现接口
enum HttpStatus implements Describeable {
    // 枚举常量
    OK(200, "请求成功"),
    CREATED(201, "资源创建成功"),
    BAD_REQUEST(400, "客户端错误"),
    UNAUTHORIZED(401, "未授权"),
    FORBIDDEN(403, "禁止访问"),
    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;
    }
    
    // 实现接口方法
    @Override
    public String getDescription() {
        return description;
    }
    
    @Override
    public String getCategory() {
        if (code >= 200 && code < 300) {
            return "成功";
        } else if (code >= 400 && code < 500) {
            return "客户端错误";
        } else if (code >= 500) {
            return "服务器错误";
        }
        return "其他";
    }
    
    // 其他方法
    public int getCode() {
        return code;
    }
    
    public boolean isSuccess() {
        return code >= 200 && code < 300;
    }
    
    public boolean isError() {
        return code >= 400;
    }
    
    // 根据状态码查找
    public static HttpStatus fromCode(int code) {
        for (HttpStatus status : values()) {
            if (status.code == code) {
                return status;
            }
        }
        throw new IllegalArgumentException("无效的状态码: " + code);
    }
}

public class HttpStatusDemo {
    public static void main(String[] args) {
        System.out.println("=== HTTP 状态码 ===");
        
        // 遍历所有状态码
        for (HttpStatus status : HttpStatus.values()) {
            System.out.printf("状态码: %d, 描述: %s, 分类: %s, 成功: %s%n",
                status.getCode(), 
                status.getDescription(),
                status.getCategory(),
                status.isSuccess());
        }
        
        // 使用状态码
        HttpStatus response = HttpStatus.OK;
        System.out.println("\n当前响应: " + response.getDescription());
        
        // 根据状态码查找
        try {
            HttpStatus found = HttpStatus.fromCode(404);
            System.out.println("查找到: " + found.getDescription());
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }
}

2. 枚举中的抽象方法

// 使用抽象方法让每个枚举常量有自己的行为
enum LoggerLevel {
    DEBUG {
        @Override
        public void log(String message) {
            System.out.println("[DEBUG] " + message);
        }
        
        @Override
        public boolean shouldLog(LoggerLevel currentLevel) {
            // DEBUG级别记录所有日志
            return true;
        }
    },
    INFO {
        @Override
        public void log(String message) {
            System.out.println("[INFO] " + message);
        }
        
        @Override
        public boolean shouldLog(LoggerLevel currentLevel) {
            // INFO级别记录INFO及以上
            return currentLevel.ordinal() >= INFO.ordinal();
        }
    },
    WARN {
        @Override
        public void log(String message) {
            System.out.println("[WARN] " + message);
        }
        
        @Override
        public boolean shouldLog(LoggerLevel currentLevel) {
            // WARN级别记录WARN及以上
            return currentLevel.ordinal() >= WARN.ordinal();
        }
    },
    ERROR {
        @Override
        public void log(String message) {
            System.out.println("[ERROR] " + message);
        }
        
        @Override
        public boolean shouldLog(LoggerLevel currentLevel) {
            // ERROR级别只记录ERROR
            return currentLevel == ERROR;
        }
    };
    
    // 抽象方法
    public abstract void log(String message);
    public abstract boolean shouldLog(LoggerLevel currentLevel);
    
    // 日志记录器
    private static LoggerLevel currentLevel = INFO;
    
    public static void setLevel(LoggerLevel level) {
        currentLevel = level;
    }
    
    public static void log(LoggerLevel level, String message) {
        if (level.shouldLog(currentLevel)) {
            level.log(message);
        }
    }
    
    // 便捷方法
    public static void debug(String message) {
        log(DEBUG, message);
    }
    
    public static void info(String message) {
        log(INFO, message);
    }
    
    public static void warn(String message) {
        log(WARN, message);
    }
    
    public static void error(String message) {
        log(ERROR, message);
    }
}

public class LoggerDemo {
    public static void main(String[] args) {
        System.out.println("=== 日志系统演示 ===");
        
        // 设置日志级别
        System.out.println("\n1. 设置为DEBUG级别(记录所有):");
        LoggerLevel.setLevel(LoggerLevel.DEBUG);
        LoggerLevel.debug("这是调试信息");
        LoggerLevel.info("这是一般信息");
        LoggerLevel.warn("这是警告信息");
        LoggerLevel.error("这是错误信息");
        
        System.out.println("\n2. 设置为WARN级别(只记录WARN和ERROR):");
        LoggerLevel.setLevel(LoggerLevel.WARN);
        LoggerLevel.debug("这条不会被记录");
        LoggerLevel.info("这条也不会被记录");
        LoggerLevel.warn("警告信息被记录");
        LoggerLevel.error("错误信息被记录");
        
        System.out.println("\n3. 直接使用枚举:");
        LoggerLevel.DEBUG.log("直接调用DEBUG");
        LoggerLevel.ERROR.log("直接调用ERROR");
    }
}

3. 枚举单例模式(最佳实践)

/**
 * 枚举实现单例模式 - 最佳方式
 * 优点:
 * 1. 线程安全
 * 2. 防止反射攻击
 * 3. 防止反序列化创建新实例
 * 4. 代码简洁
 */
enum DatabaseConnection {
    // 单例实例
    INSTANCE;
    
    private Connection connection;
    
    // 枚举构造器自动private
    DatabaseConnection() {
        System.out.println("数据库连接初始化");
        // 实际项目中这里会创建真实的数据库连接
        this.connection = createConnection();
    }
    
    private Connection createConnection() {
        // 创建数据库连接
        // 返回模拟连接
        return new Connection();
    }
    
    public Connection getConnection() {
        return connection;
    }
    
    public void executeQuery(String sql) {
        System.out.println("执行SQL: " + sql);
        // 执行查询逻辑
    }
    
    // 模拟连接类
    static class Connection {
        public void close() {
            System.out.println("关闭连接");
        }
    }
}

public class EnumSingletonDemo {
    public static void main(String[] args) {
        System.out.println("=== 枚举单例模式 ===");
        
        // 获取单例实例
        DatabaseConnection instance1 = DatabaseConnection.INSTANCE;
        DatabaseConnection instance2 = DatabaseConnection.INSTANCE;
        
        // 验证单例
        System.out.println("instance1 == instance2: " + (instance1 == instance2));
        
        // 使用单例
        instance1.executeQuery("SELECT * FROM users");
        
        // 防止反射攻击测试
        System.out.println("\n尝试反射攻击:");
        try {
            // 枚举不能通过反射创建实例
            java.lang.reflect.Constructor<DatabaseConnection> constructor = 
                DatabaseConnection.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            DatabaseConnection illegal = constructor.newInstance();
        } catch (Exception e) {
            System.out.println("反射攻击失败: " + e.getMessage());
        }
    }
}

🔧 枚举的实用技巧

1. 枚举与switch的完美配合

enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
    
    public boolean isWeekday() {
        switch (this) {
            case SATURDAY:
            case SUNDAY:
                return false;
            default:
                return true;
        }
    }
    
    public String getWorkMessage() {
        switch (this) {
            case MONDAY:
                return "周一综合症";
            case FRIDAY:
                return "感谢上帝,今天是星期五";
            case SATURDAY:
            case SUNDAY:
                return "周末愉快!";
            default:
                return "努力工作";
        }
    }
    
    public Day nextDay() {
        switch (this) {
            case MONDAY:    return TUESDAY;
            case TUESDAY:   return WEDNESDAY;
            case WEDNESDAY: return THURSDAY;
            case THURSDAY:  return FRIDAY;
            case FRIDAY:    return SATURDAY;
            case SATURDAY:  return SUNDAY;
            case SUNDAY:    return MONDAY;
            default:        throw new IllegalStateException();
        }
    }
}

public class DayDemo {
    public static void main(String[] args) {
        Day today = Day.MONDAY;
        
        System.out.println("=== 工作日判断 ===");
        for (Day day : Day.values()) {
            System.out.printf("%s: %s, %s%n", 
                day, 
                day.isWeekday() ? "工作日" : "休息日",
                day.getWorkMessage());
        }
        
        System.out.println("\n=== 下一天测试 ===");
        Day current = Day.FRIDAY;
        System.out.println("今天是: " + current);
        System.out.println("明天是: " + current.nextDay());
        System.out.println("后天是: " + current.nextDay().nextDay());
    }
}

2. 枚举作为状态机

// 订单状态机
enum OrderStatus {
    // 定义所有状态
    CREATED {
        @Override
        public OrderStatus next() {
            return PAID;
        }
        
        @Override
        public boolean canCancel() {
            return true;
        }
    },
    PAID {
        @Override
        public OrderStatus next() {
            return SHIPPED;
        }
        
        @Override
        public boolean canCancel() {
            return true;
        }
    },
    SHIPPED {
        @Override
        public OrderStatus next() {
            return DELIVERED;
        }
        
        @Override
        public boolean canCancel() {
            return false;  // 已发货不能取消
        }
    },
    DELIVERED {
        @Override
        public OrderStatus next() {
            return COMPLETED;
        }
        
        @Override
        public boolean canCancel() {
            return false;
        }
    },
    COMPLETED {
        @Override
        public OrderStatus next() {
            return this;  // 已完成是最终状态
        }
        
        @Override
        public boolean canCancel() {
            return false;
        }
    },
    CANCELLED {
        @Override
        public OrderStatus next() {
            return this;  // 已取消是最终状态
        }
        
        @Override
        public boolean canCancel() {
            return false;
        }
    };
    
    // 抽象方法:下一个状态
    public abstract OrderStatus next();
    
    // 抽象方法:是否可以取消
    public abstract boolean canCancel();
    
    // 状态转移方法
    public OrderStatus transition() {
        return next();
    }
    
    // 取消订单
    public OrderStatus cancel() {
        if (canCancel()) {
            return CANCELLED;
        }
        throw new IllegalStateException("当前状态不能取消订单: " + this);
    }
}

// 订单类
class Order {
    private String orderId;
    private OrderStatus status;
    
    public Order(String orderId) {
        this.orderId = orderId;
        this.status = OrderStatus.CREATED;
    }
    
    public void pay() {
        status = status.transition();
        System.out.println("订单已支付,新状态: " + status);
    }
    
    public void ship() {
        status = status.transition();
        System.out.println("订单已发货,新状态: " + status);
    }
    
    public void deliver() {
        status = status.transition();
        System.out.println("订单已送达,新状态: " + status);
    }
    
    public void complete() {
        status = status.transition();
        System.out.println("订单已完成,新状态: " + status);
    }
    
    public void cancel() {
        try {
            status = status.cancel();
            System.out.println("订单已取消,新状态: " + status);
        } catch (IllegalStateException e) {
            System.out.println("取消失败: " + e.getMessage());
        }
    }
    
    public OrderStatus getStatus() {
        return status;
    }
    
    public void printStatus() {
        System.out.printf("订单 %s 状态: %s%n", orderId, status);
    }
}

public class OrderStateMachine {
    public static void main(String[] args) {
        System.out.println("=== 订单状态机演示 ===");
        
        Order order = new Order("20230001");
        order.printStatus();
        
        // 正常流程
        System.out.println("\n正常流程:");
        order.pay();
        order.ship();
        order.deliver();
        order.complete();
        
        // 创建新订单测试取消
        System.out.println("\n测试取消订单:");
        Order order2 = new Order("20230002");
        order2.printStatus();
        
        order2.pay();
        order2.printStatus();
        order2.cancel();  // 可以取消
        
        order2.printStatus();
        
        // 尝试取消已发货的订单
        System.out.println("\n测试取消已发货订单:");
        Order order3 = new Order("20230003");
        order3.pay();
        order3.ship();
        order3.cancel();  // 应该失败
    }
}

3. 枚举的策略模式

// 策略接口
interface DiscountStrategy {
    double applyDiscount(double price);
}

// 使用枚举实现策略模式
enum DiscountType implements DiscountStrategy {
    // 不同的折扣策略
    NO_DISCOUNT {
        @Override
        public double applyDiscount(double price) {
            return price;
        }
    },
    STUDENT {
        @Override
        public double applyDiscount(double price) {
            return price * 0.8;  // 学生8折
        }
    },
    MEMBER {
        @Override
        public double applyDiscount(double price) {
            return price * 0.9;  // 会员9折
        }
    },
    HOLIDAY {
        @Override
        public double applyDiscount(double price) {
            return price * 0.85; // 节日85折
        }
    },
    VIP {
        @Override
        public double applyDiscount(double price) {
            return price * 0.7;  // VIP 7折
        }
    };
}

// 商品类
class Product {
    private String name;
    private double price;
    
    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    public double calculatePrice(DiscountType discountType) {
        double discountedPrice = discountType.applyDiscount(price);
        System.out.printf("%s 原价: %.2f, %s折后: %.2f%n", 
            name, price, discountType.name(), discountedPrice);
        return discountedPrice;
    }
}

public class DiscountDemo {
    public static void main(String[] args) {
        System.out.println("=== 折扣策略演示 ===");
        
        Product laptop = new Product("笔记本电脑", 5000.0);
        Product phone = new Product("智能手机", 3000.0);
        
        // 应用不同的折扣策略
        System.out.println("\n笔记本电脑:");
        laptop.calculatePrice(DiscountType.NO_DISCOUNT);
        laptop.calculatePrice(DiscountType.STUDENT);
        laptop.calculatePrice(DiscountType.VIP);
        
        System.out.println("\n智能手机:");
        phone.calculatePrice(DiscountType.MEMBER);
        phone.calculatePrice(DiscountType.HOLIDAY);
        
        // 批量处理
        System.out.println("\n批量计算:");
        Product[] products = {laptop, phone};
        DiscountType[] discounts = DiscountType.values();
        
        for (Product product : products) {
            for (DiscountType discount : discounts) {
                product.calculatePrice(discount);
            }
            System.out.println();
        }
    }
}

📊 枚举常用方法

方法说明示例
values()返回所有枚举值数组Color.values()
valueOf()根据名称返回枚举Color.valueOf("RED")
name()返回枚举名称Color.RED.name()
ordinal()返回枚举序号Color.RED.ordinal()
compareTo()比较枚举顺序RED.compareTo(GREEN)
toString()返回枚举名称(可重写)Color.RED.toString()

💡 枚举最佳实践

1. 何时使用枚举?

// ✅ 适合使用枚举的场景:

// 1. 有限的状态集合
enum OrderStatus { PENDING, PAID, SHIPPED, DELIVERED }

// 2. 错误代码
enum ErrorCode { 
    SUCCESS(0, "成功"),
    NOT_FOUND(404, "未找到"),
    UNAUTHORIZED(401, "未授权");
}

// 3. 配置选项
enum LogLevel { DEBUG, INFO, WARN, ERROR }

// 4. 单例模式
enum Singleton { INSTANCE }

// 5. 策略模式
enum Operation { ADD, SUBTRACT, MULTIPLY, DIVIDE }

2. 枚举设计建议

// 好的枚举设计
public enum GoodEnum {
    // 1. 常量名全大写
    CONSTANT_ONE("描述1"),
    CONSTANT_TWO("描述2");
    
    // 2. 私有final字段
    private final String description;
    
    // 3. 私有构造器
    private GoodEnum(String description) {
        this.description = description;
    }
    
    // 4. Getter方法
    public String getDescription() {
        return description;
    }
    
    // 5. 业务方法
    public boolean isValid() {
        return this != CONSTANT_TWO;
    }
    
    // 6. 静态工具方法
    public static GoodEnum fromDescription(String desc) {
        for (GoodEnum e : values()) {
            if (e.description.equals(desc)) {
                return e;
            }
        }
        return null;
    }
}

// ❌ 不要这样做
public enum BadEnum {
    // 不要使用无意义的名称
    A, B, C,
    
    // 不要暴露可变字段
    value;  // 应该是private final
    
    // 不要在枚举中维护可变状态
    private static int counter;  // 危险!
    
    // 不要在构造器中做复杂操作
    BadEnum() {
        // 可能抛出异常
    }
}

3. 枚举性能考虑

public class EnumPerformance {
    // 枚举比较的性能
    enum Size { SMALL, MEDIUM, LARGE }
    
    public static void main(String[] args) {
        Size size = Size.MEDIUM;
        
        // ✅ 推荐:使用 == 比较(性能好)
        if (size == Size.MEDIUM) {
            // 使用 == 比较枚举常量
        }
        
        // ✅ 推荐:使用 switch(性能好)
        switch (size) {
            case SMALL: break;
            case MEDIUM: break;
            case LARGE: break;
        }
        
        // ❌ 不推荐:使用 equals(性能稍差)
        if (size.equals(Size.MEDIUM)) {
            // 也可以,但性能不如 ==
        }
        
        System.out.println("枚举性能提示:");
        System.out.println("1. 使用 == 比较枚举,不要用 equals()");
        System.out.println("2. 枚举的 switch 语句非常高效");
        System.out.println("3. 枚举的 values() 方法每次返回新数组,需要时缓存");
        System.out.println("4. 大量使用枚举不会影响性能");
    }
}

🎓 总结要点

  1. 枚举本质是类:继承自 java.lang.Enum
  2. 构造器私有:不能手动创建枚举实例
  3. 线程安全:枚举实例在类加载时创建
  4. 支持接口:枚举可以实现接口
  5. 支持方法:可以有属性、方法、构造器
  6. 适合单例:枚举是实现单例的最佳方式
  7. 类型安全:编译时检查,避免错误值

记住:当需要表示一组固定的常量时,优先考虑使用枚举!