枚举类型(Enum)是在 JDK 1.5 中引入的。引入枚举主要是为了提供一种类型安全的常量表示方式,替代传统的静态常量。通过枚举,可以定义一组固定的常量,并提供与这些常量相关的方法和字段。
枚举基本使用
▪ 定义枚举
使用 enum class 关键字可以定义一个枚举类。而枚举类内部第一行用逗号分隔的字符串为枚举常量,下面例子中,每个常量代表一周中的某一天。
package com.cango.enum
public enum class Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
▪ 使用枚举
可以像使用静态常量一样使用枚举即通过 Day.MONDAY 等语法访问枚举常量。
public static void main(String[] args) {
Day day = Day.MONDAY;
System.out.println("今天是: " + day);
System.out.println("枚举类的类型是:" + Day.class);
System.out.println("枚举类的常量和字符串一样吗?"+"MONDAY".equals(day));
}
输出结果如下:
今天是: MONDAY
枚举类的类型是:class com.cango.enums.Day
枚举类的常量和字符串一样吗?false
那枚举到底是个啥?
枚举的本质
使用 JAD 工具反编译枚举类,输出如下:
package com.cango.enums;
public final class Day extends Enum
{
public static Day[] values()
{
return (Day[])$VALUES.clone();
}
public static Day valueOf(String name)
{
return (Day)Enum.valueOf(com/cango/enums/Day, name);
}
private Day(String s, int i)
{
super(s, i);
}
private static Day[] $values()
{
return (new Day[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
public static final Day MONDAY = new Day("MONDAY", 0);
public static final Day TUESDAY = new Day("TUESDAY", 1);
public static final Day WEDNESDAY = new Day("WEDNESDAY", 2);
public static final Day THURSDAY = new Day("THURSDAY", 3);
public static final Day FRIDAY = new Day("FRIDAY", 4);
public static final Day SATURDAY = new Day("SATURDAY", 5);
public static final Day SUNDAY = new Day("SUNDAY", 6);
private static final Day $VALUES[] = $values();
}
可以看到,枚举类和枚举常量事实上都是继承自 java.lang.Enum 类,作为所有枚举类型的公共基类。每一个枚举类型都隐式地继承自 java.lang.Enum,也就是说它有的能力,枚举类也有,枚举常量也有,先看看它的结构和能做什么事情。
java.lang.Enum
package java.lang;
public abstract class Enum<E extends Enum<E>>
implements Constable, Comparable<E>, Serializable
java.lang.Enum 是一个抽象类,成员属性如下:
⨳ String name:枚举常量的名称,在枚举声明中定义,如 "MONDAY";
⨳ int ordinal:枚举常量的序数,默认是该常量在枚举声明中的位置,从零开始,如 "MONDAY" 第一个声明,那它的 ordinal 就是 0;
知道了枚举的属性,再看枚举常量就不迷惑了,MONDAY 的本质是 {name="MONDAY",ordinal=0}, TUESDAY 的本质是 {name="TUESDAY",ordinal=1} ... 而枚举的构造方法就是为这两个属性赋值的:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
既然枚举常量是 java.lang.Enum 类,和字符串类型进行比较会返回 false,这我能理解,那为啥打印出来的是字符串呢?打印默认调用的是 toString 方法,那就看它的 toString 方法呗:
public String toString() {
return name;
}
一切尽在不言中....
那枚举和普通类相比还有什么特性呢?
⨳ 只有相同类型的枚举常量才能进行比较,而且比较的是 ordinal 不是 name:
public final int compareTo(E o) {
Enum<?> other = o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
⨳ 不能被反序列化
@java.io.Serial
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
这一点在《单例模式》 一文中也讲到了, 这里节选如下:
在Java中,枚举类型的实例是在类加载时被创建的,并且在整个应用程序生命周期中只会有一个实例存在。当枚举类型的实例被序列化时,实际上是将枚举常量的名称写入到序列化输出流中,而不是将对象的状态写入。
在反序列化过程中,Java虚拟机会根据枚举常量的名称来查找对应的实例,而不是重新创建一个新的实例。这样就保证了在反序列化时获得的对象是相同的枚举实例,不会破坏枚举的单例性质。
枚举类
了解了 java.lang.Enum ,再看自定义的枚举类就很清晰了:
public static final Day MONDAY = new Day("MONDAY", 0);
public static final Day TUESDAY = new Day("TUESDAY", 1);
public static final Day WEDNESDAY = new Day("WEDNESDAY", 2);
public static final Day THURSDAY = new Day("THURSDAY", 3);
public static final Day FRIDAY = new Day("FRIDAY", 4);
public static final Day SATURDAY = new Day("SATURDAY", 5);
public static final Day SUNDAY = new Day("SUNDAY", 6);
声明的枚举常量就是自定义枚举类型的一个静态常量对象而且,name 自己定义,ordinal 序号自动生成。
那既然枚举常量的类型就是自定义枚举类型,自定义枚举类型继承自 java.lang.Enum ,那是不是可以在自定义枚举的时候按需添加独属于自己的成员呢?
那是肯定的。
自定义枚举的方法
▪ 定义枚举
package com.cango.enums;
public enum TrafficLight {
RED(30),
GREEN(60),
YELLOW(5);
private int duration;
TrafficLight(int duration) {
this.duration = duration;
}
public int getDuration() {
return duration;
}
}
如上,定义了一个 TrafficLight 类型的枚举,TrafficLight作为枚举类,除了继承的 name 和 ordinal 属性外,还自定义了一个属性 duration,用于表示交通灯的持续时间。
重写的 TrafficLight 构造方法,看似没有为 name 和 ordinal 属性赋值,但实际上编译器会自动覆盖这个构造方法的,JAD反编译节选如下:
public final class TrafficLight extends Enum
{
public static final TrafficLight RED = new TrafficLight("RED", 0, 30);
public static final TrafficLight GREEN = new TrafficLight("GREEN", 1, 60);
public static final TrafficLight YELLOW = new TrafficLight("YELLOW", 2, 5);
private int duration;
private TrafficLight(String s, int i, int duration)
{
super(s, i);
this.duration = duration;
}
public int getDuration()
{
return duration;
}
}
▪ 使用枚举
for (TrafficLight light : TrafficLight.values()) {
System.out.println(light + ": " + light.getDuration() + " seconds");
}
输出结果如下:
RED: 30 seconds
GREEN: 60 seconds
YELLOW: 5 seconds
总结
枚举提供了一种简洁、易读的方式来定义一组相关的常量,避免了使用 public static final 定义常量的繁琐。
⨳ 类型安全:枚举类型在编译时提供类型安全检查,防止非法的常量赋值。
⨳ 可读性:枚举使代码更具可读性和可维护性,因为它们将一组相关的常量集合在一个类型中。
⨳ 自动序列化:枚举类型的实例序列化和反序列化是自动处理的,减少了代码的复杂性。
总结来说,枚举提供了一组预定义的、相关联的常量,使用枚举会提高代码的可读性和类型安全性,常用于需要一组固定选项的场合,比如时间单元(天、小时、分钟)、工作日、季节等。