Skip to content

枚举

概述

枚举(enum)是 Java 中表示固定一组常量的特殊类型。它既是一种数据类型,也是一种特殊的类:由 JVM 保证每个枚举常量全局唯一、线程安全,且自带 nameordinal 等能力。相比用 public static final int 或字符串常量表示状态/类型,枚举更类型安全、可读性更好,且能携带字段和方法,适合表达「有限的几种取值」(如星期、季节、订单状态、权限级别等)。

通俗理解:枚举就是「只能取预定几个值」的类型;每个值是一个枚举常量,本质是该枚举类的单例实例。学习枚举有助于写出更清晰、更少魔法数字的代码,并为后续学习 注解反射 打基础。

前置建议

已掌握 类与对象构造方法封装,理解类与实例、访问修饰符。


为什么需要枚举

  • 类型安全:用枚举变量只能赋枚举常量,编译期即可发现错误;用 intString 容易传入非法值。
  • 可读性DayOfWeek.MONDAY 比数字 1 或字符串 "MONDAY" 更直观。
  • 单例与唯一性:每个枚举常量在 JVM 中唯一,天然适合做单例或固定集合。
  • 可扩展:枚举可以有成员变量、构造方法、普通方法,甚至实现接口,而不只是「名字 + 序号」。

基本语法

简单枚举

使用 enum 关键字定义,枚举常量用逗号分隔,末尾分号可选(无其他成员时可省略)。

java
// 最简单的枚举:仅列举常量名
public enum DayOfWeek {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

// 使用
DayOfWeek today = DayOfWeek.FRIDAY;
System.out.println(today);           // FRIDAY
System.out.println(today.name());   // FRIDAY(枚举常量名)
System.out.println(today.ordinal()); // 4(声明顺序,从 0 开始)

提示

枚举常量命名习惯使用全大写、多个单词用下划线(如 MAX_PRIORITY),与常量命名一致。

枚举是类

枚举编译后继承 java.lang.Enum,因此不能再用 extends 继承其他类(Java 单继承);但可以实现接口。每个枚举常量本质上是该枚举类的一个实例,由 JVM 在类加载时创建,且全局唯一。

java
public enum Size {
    SMALL, MEDIUM, LARGE
}

// 等价于(概念上):每个常量都是 Size 的实例
// Size SMALL = new Size("SMALL", 0);
// Size MEDIUM = new Size("MEDIUM", 1);
// Size LARGE = new Size("LARGE", 2);

带字段与方法的枚举

枚举可以有构造方法成员变量普通方法。构造方法必须是 包私有或 private,不能从外部 new 枚举实例;常量声明时相当于调用该构造方法。

示例 1:为每个常量绑定描述

java
public enum Season {
    SPRING("春天", "温暖"),
    SUMMER("夏天", "炎热"),
    AUTUMN("秋天", "凉爽"),
    WINTER("冬天", "寒冷");

    private final String nameCn;
    private final String desc;

    // 枚举的构造方法:不能是 public,由常量列表隐式调用
    Season(String nameCn, String desc) {
        this.nameCn = nameCn;
        this.desc = desc;
    }

    public String getNameCn() {
        return nameCn;
    }

    public String getDesc() {
        return desc;
    }
}

// 使用
Season s = Season.SUMMER;
System.out.println(s.getNameCn() + " " + s.getDesc());  // 夏天 炎热

示例 2:枚举中定义抽象方法(每个常量不同行为)

每个枚举常量可以重写枚举类中的抽象方法,实现「常量即策略」。

java
public enum Operation {
    PLUS {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS {
        @Override
        public double apply(double x, double y) {
            return x - y;
        }
    },
    MULTIPLY {
        @Override
        public double apply(double x, double y) {
            return x * y;
        }
    };

    // 枚举内的抽象方法:每个常量都必须提供实现
    public abstract double apply(double x, double y);
}

// 使用
double r = Operation.PLUS.apply(2, 3);   // 5.0
double r2 = Operation.MULTIPLY.apply(2, 3); // 6.0

常用 API 与 switch

name、ordinal、valueOf、values

  • name():枚举常量的名称(与声明时一致)。
  • ordinal():声明顺序(从 0 开始);不建议用 ordinal 表达业务含义,仅作内部顺序
  • valueOf(String name):根据名称得到枚举常量,名称不存在时抛出 IllegalArgumentException
  • values():返回该枚举所有常量的数组(静态方法,由编译器生成)。
java
DayOfWeek[] all = DayOfWeek.values();
for (DayOfWeek d : all) {
    System.out.println(d.name() + " " + d.ordinal());
}

DayOfWeek d = DayOfWeek.valueOf("MONDAY");  // 名称必须完全匹配

在 switch 中使用

枚举与 switch 配合非常自然,且能利用编译期检查避免遗漏分支。

java
public static int getWorkHours(DayOfWeek day) {
    switch (day) {
        case MONDAY:
        case TUESDAY:
        case WEDNESDAY:
        case THURSDAY:
        case FRIDAY:
            return 8;
        case SATURDAY:
        case SUNDAY:
            return 0;
        default:
            throw new IllegalArgumentException("Unknown: " + day);
    }
}

注意

使用枚举的 ordinal() 表示业务含义(如数据库存 0/1/2)容易在调整常量顺序时出错,建议用自定义字段(如 int code)或 name() 持久化。


实现接口

枚举可以实现一个或多个接口,让枚举类型具备统一的行为契约。

java
public interface Describable {
    String getDescription();
}

public enum Color implements Describable {
    RED("红色"),
    GREEN("绿色"),
    BLUE("蓝色");

    private final String desc;

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

    @Override
    public String getDescription() {
        return desc;
    }
}

// 接口引用指向枚举常量
Describable d = Color.RED;
System.out.println(d.getDescription());  // 红色

注意事项

  • 构造方法:枚举的构造方法不能是 public,只能是包私有或 private,且不能手动 new 枚举实例。
  • 常量顺序:枚举常量必须写在类体最前面,若有字段或方法,常量列表后要有分号。
  • ordinal 的陷阱:不要依赖 ordinal() 做业务编码;新增或调整常量顺序会导致值变化,应使用显式字段(如 int code)或 name()
  • 单例:每个枚举常量在 JVM 中唯一,反序列化也不会产生新实例,因此枚举常被用来实现线程安全的单例(如 Effective Java 推荐方式)。
  • 比较:枚举常量比较应使用 ==(同一实例),不必用 equals;若需按名称比较,可用 name().equals(...)valueOf

提示

需要「按名称查找」时用 valueOf;需要「遍历所有常量」时用 values()。在集合框架中,EnumSetEnumMap 针对枚举做了优化,可优先使用。


相关链接

基于 VitePress 构建