Skip to content

枚举类型

前置知识

在阅读本章前,你需要了解:Java 基础语法,类和对象的概念,基本的集合框架使用。

为什么需要枚举类型?

想象一下,你在写一个程序,需要用代码表示一年中的几个月、车辆的几种颜色,或者订单的状态。用普通的 intString 来代表这些状态看似简单,但问题来了——你怎么保证不会写错?比如,写成 "Bleu" 代替 "Blue"?或者用 3 来表示未定义的状态?

枚举(enum)就是为了解决这类问题的。它帮我们定义一组固定的常量,每个常量都有名字,而且类型安全。这样,编译器就能帮你检查错误,代码更健壮,更易读。

不止如此,Java 的枚举比一般语言中的枚举更强大——它们其实是带有方法和字段的特殊类。学习好枚举,将让你写出更加优雅和灵活的代码。

具体章节

1. 什么是枚举?怎么定义?

简单来说,枚举就是一组事先定义好的“有限选择”。用人话说,枚举就是给相关常量命名的“专属容器”。

最简单的定义方式是:

java
// 定义一个表示星期几的枚举
public enum DayOfWeek {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

这段代码定义了一个名字叫 DayOfWeek 的枚举,它包含了7个可选项。每个都是 DayOfWeek 类型的实例。

这段代码做了什么:

  • DayOfWeek 是一个特殊的类,预设了7个实例。
  • 你不能再添加新的“星期几”,这就是枚举“固定选择”的体现。
  • 编译器会帮你限制使用范围。

如何用它?

java
public class EnumBasicExample {
    public static void main(String[] args) {
        DayOfWeek today = DayOfWeek.WEDNESDAY;

        if (today == DayOfWeek.SATURDAY || today == DayOfWeek.SUNDAY) {
            System.out.println("周末,放松时光!");
        } else {
            System.out.println("工作日,加油!");
        }
    }
}

这段代码做了什么:

  • 我们定义变量 today 类型是 DayOfWeek
  • 判断是否是周末,打印不同的消息。
  • 类型安全,不能传入非枚举定义的值。

2. 枚举里能干啥?构造器和方法

这里开始有趣了:Java 的枚举其实是类,你可以给它定义字段、构造器,甚至重写方法。举个常见例子——给枚举加属性。

java
public enum Planet {
    MERCURY(3.303e+23, 2.4397e6),
    VENUS  (4.869e+24, 6.0518e6),
    EARTH  (5.976e+24, 6.37814e6),
    MARS   (6.421e+23, 3.3972e6);

    private final double mass;   // 单位:千克
    private final double radius; // 单位:米

    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }

    public double mass() { return mass; }
    public double radius() { return radius; }

    // 计算星球表面重力加速度
    public double surfaceGravity() {
        final double G = 6.67300E-11;
        return G * mass / (radius * radius);
    }
}

这段代码做了什么:

  • Planet 枚举每个实例都带着质量和半径两个数据。
  • 构造函数声明成私有(枚举隐式私有),给每个常量初始化属性。
  • 定义方法 surfaceGravity() 计算物理量。

使用示例:

java
public class EnumWithFieldsAndMethods {
    public static void main(String[] args) {
        Planet earth = Planet.EARTH;
        System.out.printf("地球的表面重力是:%.2f m/s²%n", earth.surfaceGravity());
    }
}

这段代码做了什么:

  • 创建引用指向 EARTH 实例。
  • 调用自定义方法获得计算值。

3. 枚举的抽象方法:个性化行为

有时候,我们想让每个枚举实例表现得不完全一样,这时 Java 支持在枚举中声明抽象方法,并由每个枚举实例单独实现。

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;
        }
    },
    TIMES {
        @Override
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE {
        @Override
        public double apply(double x, double y) {
            return x / y;
        }
    };

    public abstract double apply(double x, double y);
}

这段代码做了什么:

  • 声明 Operation 枚举代表操作。
  • 每个常量都实现了抽象方法 apply 来完成对应运算。
  • 枚举实例有自己的行为逻辑。

试用它:

java
public class EnumAbstractMethodExample {
    public static void main(String[] args) {
        double x = 10, y = 5;
        for (Operation op : Operation.values()) {
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
    }
}

这段代码做了什么:

  • 遍历所有操作。
  • 调用每个实例的 apply,打印运算结果。

4. 枚举专属集合:EnumSet 和 EnumMap

使用枚举时,你会发现手写 Set<DayOfWeek>Map<DayOfWeek, String> 很常见。Java 专门提供了 EnumSetEnumMap,它们是基于枚举优化过的集合,性能和内存比常规集合要优秀不少。

EnumSet 用于存储枚举类型的集合,内部底层通常使用位运算,非常高效。

java
import java.util.EnumSet;

public class EnumSetExample {
    enum Color {
        RED, GREEN, BLUE
    }

    public static void main(String[] args) {
        EnumSet<Color> primaryColors = EnumSet.of(Color.RED, Color.BLUE);

        System.out.println("Primary colors:");
        for (Color color : primaryColors) {
            System.out.println(color);
        }
    }
}

这段代码做了什么:

  • 创建一个只包含部分枚举值的集合。
  • 遍历输出。

EnumMap 用于键是枚举的映射,内部使用数组实现,查找速度极快。

java
import java.util.EnumMap;

public class EnumMapExample {
    enum Day {
        MONDAY, TUESDAY, WEDNESDAY
    }

    public static void main(String[] args) {
        EnumMap<Day, String> schedule = new EnumMap<>(Day.class);

        schedule.put(Day.MONDAY, "开会");
        schedule.put(Day.TUESDAY, "编写代码");
        schedule.put(Day.WEDNESDAY, "测试");

        for (Day day : schedule.keySet()) {
            System.out.println(day + ": " + schedule.get(day));
        }
    }
}

这段代码做了什么:

  • 创建以 Day 枚举为键的映射。
  • 给每天安排活动。
  • 依次打印。

⚠️ 常见陷阱

  • 不要在枚举中声明 public 构造器
    枚举隐含私有构造器,显式写成 public 会导致编译错误。

  • 避免使用 == 比较非同一个枚举类型实例
    == 比较时,必须保证两者都是同一个枚举类型的实例,否则会逻辑混乱。

  • 不要尝试继承枚举
    枚举类隐式继承 java.lang.Enum,且不能被继承。你不能写 class X extends Enum

  • 枚举常量定义顺序很重要
    枚举实例初始化顺序是固定的,和代码出现顺序一致,某些依赖顺序初始化的逻辑可能会被打乱。


💡 实战建议

  • 在设计状态机、事件类型、配置常量时优先考虑使用枚举,能让代码更安全、易维护。
  • 利用枚举的字段和方法,可以把状态相关的行为封装得更清晰,避免大量 if-elseswitch
  • EnumSetEnumMap 能极大提升对枚举集合操作的效率,建议代替常规的 HashSetHashMap
  • 不推荐将枚举长时间序列化为数据库中的数字存储,应该存储枚举的名字(name()),避免代码重排导致的序号错乱。
  • 枚举实现接口时,接口方法可以被所有实例共享,也可以为特定枚举实例单独实现,灵活性强。

🔍 深入理解 枚举类型的本质

Java 中的枚举类是 java.lang.Enum 的子类。其实枚举是有限个对象的集合,这些对象在编译时就被固定下来。每个枚举实例是 public static final 的。这样设计避免了不一致性。

此外,枚举还提供了 .values() 静态方法返回枚举所有实例列表,.valueOf(String name) 静态方法依据名字获取对应实例,极大便捷。


小结

  • 枚举就是一组固定常量,实现类型安全的有限状态集。
  • Java 枚举本质是类,可以有字段、构造器和方法。
  • 可以为每个枚举实例实现不同的行为(抽象方法)。
  • 使用 EnumSetEnumMap 操作枚举集合性能更好。
  • 设计枚举时要避免常见坑点,并利用它的强大特性写出优雅代码。