枚举类型

Enums 是什么?

枚举是所有实例均被编译器所知的类。它们用于创建仅能取少数可能值的类型。
枚举的创建方式与类类似,但需使用枚举关键字(enum)替代类关键字(class)。枚举主体中包含一组称为枚举常量的实例列表,各常量以逗号分隔。除枚举常量之外,无法创建枚举的其他实例。

public enum DayOfWeek {
    // enum constants are listed here:
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

所有枚举类型都隐式继承自 java.lang.Enum,且不能有任何子类。

访问、计算和比较枚举类型

枚举的值可作为常量使用。要检查两个枚举实例是否相等,可使用==运算符

DayOfWeek day = DayOfWeek.MONDAY;
if(weekStart == DayOfWeek.MONDAY) {
    IO.println("The week starts on Monday.");
}

也可以使用switch语句根据枚举值的不同来执行相应的操作。

DayOfWeek someDay = DayOfWeek.FRIDAY
switch (someDay) {
    case MONDAY -> IO.println("The week just started.");
    case TUESDAY,WEDNESDAY,THURSDAY -> IO.println("We are somewhere in the middle of the week.");
    case FRIDAY -> IO.println("The weekend is near.");
    case SATURDAY,SUNDAY -> IO.println("Weekend");
    default -> throw new  AssertionError("Should not happen");
}

使用switch expressions,编译器可检查枚举的所有值是否均被处理。若switch expressions遗漏任何可能的值,将引发编译器错误。此特性称为穷尽性,也可通过JEP 409:Sealed Classes与[pattern matching][https://dev.java/learn/pattern-matching/#switch]在常规类中实现。

DayOfWeek someDay = DayOfWeek.FRIDAY;

String text = switch (someDay) {
    case MONDAY ->
        "The week just started.";
    case TUESDAY, THURSDAY ->
        "We are somewhere in the middle of the week.";
    case FRIDAY ->
        "The weekend is near.";
    case SATURDAY, SUNDAY ->
        "Weekend";
};

上面这个例子会报错

the switch expression does not cover all possible input values

向枚举添加成员

与类类似,枚举也可以拥有构造函数、方法和字段。要添加这些内容,必须在枚举常量列表后添加分号。构造函数的参数在枚举常量声明后的括号内传递。

public enum DayOfWeek {
    // enum constants are listed here:
    MONDAY("MON"), TUESDAY("TUE"), WEDNESDAY("WED"), THURSDAY("THU"), FRIDAY("FRI"), SATURDAY("SAT"), SUNDAY("SUN");

    private final String abbreviation;

    DayOfWeek(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
        return abbreviation;
    }
}

特殊方法

所有枚举类型都隐式添加了若干方法。
例如,方法 name() 存在于所有枚举实例中,可用于获取枚举常量的名称。同样,名为 ordinal() 的方法会返回枚举常量在声明中的位置。

IO.println(DayOfWeek.MONDAY.name());    // prints "MONDAY"
IO.println(DayOfWeek.MONDAY.ordinal()); // prints "0" because MONDAY is the first constant in the DayOfWeek enum

除了实例方法外,所有枚举类型还添加了静态方法。values() 方法返回包含该枚举所有实例的数组,而 valueOf(String) 方法可通过名称获取特定实例。

DayOfWeek[] days = DayOfWeek.values();
DayOfWeek monday = DayOfWeek.valueOf("MONDAY");

此外,枚举实现了Comparable接口。默认情况下,枚举会根据其序号排序,即按枚举常量出现的顺序排列。这使得枚举实例的比较、排序和搜索成为可能。

public void compareDayOfWeek(DayOfWeek dayOfWeek) {
   int comparison = dayOfWeek.compareTo(DayOfWeek.MONDAY);
   if (comparison < 0) {
       IO.println("It's before the middle of the work week.");
   } else if (comparison > 0) {
       IO.println("It's after the start of the work week.");
   } else {
       IO.println("It's the middle of the work week.");
   }
}
List<DayOfWeek> dayList = new ArrayList<>(List.of(DayOfWeek.FRIDAY, DayOfWeek.TUESDAY, DayOfWeek.SUNDAY));
Collections.sort(dayList);

使用枚举类型创建单例

由于枚举只能拥有特定数量的实例,因此可以通过创建仅包含单个枚举常量的枚举来实现单例模式。

public enum SingletonByEnum {
    INSTANCE;
}

枚举类的抽象方法

尽管枚举无法被扩展,但它们仍可包含抽象方法。在这种情况下,每个枚举常量都必须提供具体实现。

public enum MyEnum {
    A() {
        @Override
        void doSomething() {
            IO.println("A");
        }
    },
    B() {
        @Override
        void doSomething() {
            IO.println("B");
        }
    };
    
    abstract void doSomething();
}

注意事项

在使用枚举时需谨慎,尤其当枚举项的数量(或名称)可能发生变化时。每当枚举常量被修改,其他依赖旧版枚举的代码都可能出现异常行为。这可能表现为编译错误(例如引用已被移除的枚举常量)、运行时错误(例如存在默认情况处理,但新枚举常量应单独处理)或其他不一致问题(例如枚举值被保存至文件,后续读取时仍期望该值存在)。

修改枚举常量时,建议审查所有使用该枚举的代码。当该枚举也被他人代码使用时,此项操作尤为重要。

此外,当实例数量较多时,或许值得考虑采用其他方案,因为在代码中单一位置列举大量实例可能缺乏灵活性。例如,在这种情况下,使用配置文件列出所有实例并在程序中读取这些配置文件可能更为理想。

结束

枚举提供了一种简单且安全的方式来表示一组固定的常量,同时保留了类的大部分灵活性。它们是一种特殊的类,可用于编写优雅、易读、易维护的代码,并能与其他现代Java特性(如switch表达式)良好配合。另一种特殊类是 Record,该特性在Java 16中作为预览功能引入,并在Java 16正式成为标准特性。访问我们的记录教程以了解更多信息。

要了解更多关于枚举的信息,请访问 java.lang.Enum 的 javadoc。