枚举原理与应用

面试官问:"Java 枚举是什么?"

候选人小贾答:"枚举是用来定义一组常量值的类型。"

面试官追问:"枚举在底层是怎么实现的?"

小贾说:"编译器会生成类?"

面试官继续追问:"枚举能继承其他类吗?能实现接口吗?"

小贾答不上来。

【面试官心理】 这道题考查的是候选人对 Java 枚举机制的理解深度。能说出枚举编译后生成 Enum 子类、并支持实现接口的候选人,说明对 Java 类型系统有较好理解。

一、枚举的本质 🔴

1.1 枚举是特殊的类

// 源代码
enum Color {
    RED, GREEN, BLUE
}

// 编译器生成(伪代码)
public final class Color extends Enum<Color> {
    public static final Color RED = new Color("RED", 0);
    public static final Color GREEN = new Color("GREEN", 1);
    public static final Color BLUE = new Color("BLUE", 2);

    private Color(String name, int ordinal) {
        super(name, ordinal);
    }
}

1.2 Enum 类的结构

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

    private final String name;      // 枚举常量名称
    private final int ordinal;       // 枚举顺序

    public final String name() { return name; }
    public final int ordinal() { return ordinal; }

    // 其他方法...
}

1.3 枚举的关键特性

// 1. 枚举类默认是 final(不能被继承)
// 2. 枚举构造器默认 private(只能内部调用)
// 3. 每个枚举常量都是枚举类的单例

Color c1 = Color.RED;
Color c2 = Color.RED;
c1 == c2; // true!枚举常量是单例

二、枚举的进阶用法 🔴

2.1 枚举可以有属性和方法

enum Status {
    SUCCESS(200, "成功"),
    ERROR(500, "失败"),
    NOT_FOUND(404, "未找到");

    private final int code;
    private final String message;

    Status(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() { return code; }
    public String getMessage() { return message; }

    // 静态方法
    public static Status fromCode(int code) {
        for (Status s : Status.values()) {
            if (s.code == code) return s;
        }
        return null;
    }
}

// 使用
Status s = Status.SUCCESS;
s.getCode(); // 200
Status.fromCode(404); // NOT_FOUND

2.2 枚举可以实现接口

interface Describable {
    String describe();
}

enum Operation implements Describable {
    PLUS("+") {
        @Override
        public double apply(double x, double y) { return x + y; }
    },
    MINUS("-") {
        @Override
        public double apply(double x, double y) { return x - y; }
    },
    TIMES("*") {
        @Override
        public double apply(double x, double y) { return x * y; }
    },
    DIVIDE("/") {
        @Override
        public double apply(double x, double y) {
            if (y == 0) throw new ArithmeticException();
            return x / y;
        }
    };

    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String describe() {
        return "Operation: " + symbol;
    }
}

// 使用
Operation op = Operation.PLUS;
op.apply(1, 2); // 3.0
op.describe(); // "Operation: +"

三、枚举的工程应用 🔴

3.1 单例模式(最佳实践)

// ❌ 传统单例(懒加载/线程安全写法复杂)
// ✅ 枚举单例(线程安全、自动序列化)
enum Singleton {
    INSTANCE;

    private final Connection connection;

    Singleton() {
        this.connection = createConnection();
    }

    private Connection createConnection() {
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/db");
    }

    public Connection getConnection() {
        return connection;
    }
}

// 使用
Singleton.INSTANCE.getConnection();

// 为什么枚举单例是线程安全的?
// 1. 枚举常量在类加载时由 JVM 保证只创建一次
// 2. JVM 保证了多线程环境下的安全初始化
// 3. 不需要 synchronized 或 double-check

3.2 策略模式

enum PayStrategy {
    ALIPAY {
        @Override
        public void pay(double amount) {
            System.out.println("Alipay: " + amount);
        }
    },
    WECHAT {
        @Override
        public void pay(double amount) {
            System.out.println("Wechat: " + amount);
        }
    },
    CARD {
        @Override
        public void pay(double amount) {
            System.out.println("Card: " + amount);
        }
    };

    public abstract void pay(double amount);
}

// 使用
PayStrategy.ALIPAY.pay(100.0);

3.3 状态机

enum OrderStatus {
    CREATED {
        @Override
        public OrderStatus next() { return PAID; }
    },
    PAID {
        @Override
        public OrderStatus next() { return SHIPPED; }
    },
    SHIPPED {
        @Override
        public OrderStatus next() { return DELIVERED; }
    },
    DELIVERED {
        @Override
        public OrderStatus next() { return null; } // 最终状态
    };

    public abstract OrderStatus next();
}

四、枚举的方法 🟡

// 常用方法
Color.RED.name();          // "RED"
Color.RED.ordinal();       // 0(第几个)
Color.RED.toString();      // "RED"
Color.values();           // [RED, GREEN, BLUE]
Color.valueOf("RED");     // RED(根据名称查找)
Color.RED.compareTo(Color.GREEN); // -1(ordinal 差)

五、枚举的序列化 🟡

// 枚举序列化是特殊的
// JVM 保证枚举常量只创建一个实例

// 反序列化时,JVM 直接返回枚举常量,不调用构造器
Color c = Color.RED;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(c);

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Color c2 = (Color) ois.readObject();

c == c2; // true!同一个对象
💡

枚举的序列化机制保证了反序列化后仍然是同一个对象,这是其他序列化方式无法做到的。数据库中存储枚举值(code/int)比存储枚举名称更高效。

六、追问升级

面试官:"枚举能继承其他类吗?"

// ❌ 枚举不能继承其他类
// 因为所有枚举都继承 Enum,Java 不支持多重继承
// abstract class Base { }
// enum MyEnum extends Base { } // 编译错误

// ✅ 枚举可以实现接口
// interface Runnable { }
// enum MyEnum implements Runnable { }

【面试官心理】 能说出枚举不能继承其他类但可以实现接口的候选人,说明对 Java 类型系统有基本理解。这是 P6 的要求。