Java枚举类详解与最佳实践
一、枚举类是什么
Java中的枚举(enum)是一种特殊的类,它用于定义一组常量。
二、枚举类的基本语法
1、枚举类的定义
一个简单枚举类的定义:
enum Day {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY // 这里如果只是定义这些枚举常量的话,则这里不需要分号,
// 如果后面还有其他成员,则需要分号
}
2、枚举类的使用
public class Example {
public static void main(String[] args) {
Day day = Day.MONDAY;
if(day == Day.MONDAY) {
System.out.println("It's Monday");
}
}
}
enum Day {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY // 这里如果只是定义这些枚举常量的话,则这里不需要分号,
// 如果后面还有其他成员,则需要分号
}
3、注意
- 枚举类中定义的枚举常量必须放到类体的非注释的第一行,如果有多个枚举常量,则使用逗号将它们分隔开,有多个枚举常量则可以在逗号处换行,就像数组的语法。
- 如果一个枚举类向上面的例子一样只定义了一系列枚举常量,没有其他的成员,这样最后可以不加分号,如果后面有其他的成员,则需要加分号。
- 枚举常量不需要任何访问修饰符(如 public、private 等),因为枚举常量默认是 public static final 的。
三、通过 javap 反编译工具查看枚举类的特点
使用下面的代码作示例:
enum Color {
RED, GREEN, BLUE
}
我们可以通过 javac 将文件编译得到字节码文件,然后使用 javap 反编译,可以得到以下代码:
final class Color extends java.lang.Enum<Color> {
public static final Color RED;
public static final Color GREEN;
public static final Color BLUE;
public static Color[] values();
public static Color valueOf(java.lang.String);
static {};
}
1、枚举类默认继承自 java.lang.Enum 类
从上面可以看到所有使用 enum 关键词创建的枚举类都是默认继承 java.lang.Enum 类的。
2、继承得到的方法
由于使用 enum 创建的枚举类是继承自 Enum 类的,所以这个 enum 创建的枚举类就具有了 Enum 类的一些方法:
public static Color valueOf(java.lang.String);
valueOf(String): 这是一个静态方法,根据提供的名称返回对应的枚举常量。如果没有找到匹配的常量,会抛出 IllegalArgumentException
。例如:
Color color = Color.valueOf("RED"); // 返回 Color.RED
对于枚举类继承 Enum 类得到的方法,下面还会详细介绍。
3、枚举常量特点
public static final Color RED;
public static final Color GREEN;
public static final Color BLUE;
从这里的代码可以看到,对于每个枚举常量,都是这个枚举类的实例,而且都是使用 public static final 修饰的。
4、枚举类不可被继承也不能继承其他类
final class Color extends java.lang.Enum<Color>
从这里可以看到,枚举类是 final 修饰的,所以说枚举类是不能被继承的。
由于枚举类已经继承自 java.lang.Enum 类,又由于 Java 不支持类多继承,所以枚举类也不能再继承其他类了。
5、静态代码块
可以看到代码的最后有一个静态代码块:
static {};
这个静态代码块是用于初始化枚举常量的。编译器会在这个静态代码块中创建 RED
、GREEN
和 BLUE
这三个常量的实例,并将它们注册到 Color
类中。
6、插入的方法
public static Color[] values();
values(): 这是一个静态方法,返回一个包含所有枚举常量的数组(Color
类型的数组)。这个方法通常用于迭代枚举常量。这个方法例如:
for (Color color : Color.values()) {
System.out.println(color);
}
对于这个 values() 方法,不是继承得到的,是编译器自动在使用 enum 定义的枚举类中插入的。如果去查看 Enum 类,实际上是找不到这个方法的。
四、枚举类的成员
1、枚举类的构造器
1)默认构造器
枚举有自己的构造器。如果你没有显示声明一个枚举类的构造器的话,编译器会为这个枚举类生成一个默认构造器,默认构造器是私有的。
enum Color {
RED, GREEN, BLUE
}
对于这个代码,通过反射获取的构造器为 private Color(java.lang.String,int) 这个构造器带有两个参数:一个是 String
(常量名称),另一个是 int
(常量的序号)。
这两个参数都是用来初始化父类 Enum 类中的属性的,在枚举类继承父类时,也得到了父类的两个属性,一个是 name,就是枚举常量的名字,一个是 ordinal,也就是枚举常量的序号。
这两个参数是隐藏的,因为使用 javap 的到的构造器是 private Color()。所以对于这两个参数,应当是 JVM 来传入的。所以我们可以将默认构造器看做无参构造器。
2)自定义构造器
实际上枚举类的构造器默认是私有的,如果你自己定义了一个构造器,没有加访问修饰符的话,就是默认 private 的。
enum Season {
SPRING("温暖"),
SUMMER("炎热"),
AUTUMN("凉爽"),
WINTER("寒冷");
private String description;
private Season(String description) {
this.description = description;
}
}
可以看到这里的 private 访问修饰符是多余的,所以说枚举类的构造器默认是私有的。
而且枚举类的构造器前面不能加 public 或 protected 修饰符,所以说枚举类的构造器默认且必须是私有的。
3)构造器的调用
枚举类的构造器的调用是在枚举常量后,枚举常量后加括号,括号后为传入构造器的参数,如果使用的是无参构造器,或者是编译器产生的默认构造器,则可以省略括号,上面的几个例子中足以体现这一点。
2、枚举类继承自 Enum 类的方法
1)toString() 方法
Enum 类已经重写了从 Object 类继承的 toString() 方法了,具体是:
public String toString() {
return name;
}
这里的 name 就是当前枚举常量的名字。
2)name() 方法
返回当前枚举常量的名字。
public final String name() {
return name;
}
name() 方法是不能被重写的。
3)ordinal() 方法
返回当前枚举常量的序号,从 0 开始递增。
public final int ordinal() {
return ordinal;
}
ordinal() 方法也是不能被重写的。
4)valueOf() 方法
这是一个静态方法,根据提供的名称返回对应的枚举常量。如果没有找到匹配的常量,会抛出 IllegalArgumentException
。
5)compareTo() 方法
public final int compareTo(E o) // E 为当前的枚举类
这个方法用于比较当前枚举实例与另一个相同枚举类的实例的顺序。也就是比较它们的 ordinal 属性的大小,或者说比较它们的序号大小。
返回值:
o
之前,则返回一个负整数。o
相等,则返回零。o
之后,则返回一个正整数。对于这个方法的源码,核心是:
return self.ordinal - other.ordinal;
self 是调用这个方法的枚举常量的引用,other 是传入这个方法的枚举常量的引用。
3、枚举类中编译器插入的方法
编译器会在枚举类中插入方法 values(),这个方法的声明为:
public static Color[] values();
上面我们也提到了这个方法不是继承得到的,我们在 Enum 类中并不能看到这个方法,这个方法是编译器插入的。
这个方法返回一个包含所有枚举常量的数组(Color
类型的数组)。这个方法通常用于迭代枚举常量。
4、测试以上枚举类的方法
import java.util.Arrays;
public class Example {
public static void main(String[] args) {
Season season = Season.SPRING;
System.out.println("toString(): " + season.toString());
System.out.println("name(): " + season.name());
System.out.println("ordinal(): " + season.ordinal());
System.out.println("valueOf(\"SPRING\"): " + Season.valueOf("SPRING"));
System.out.println("SPRING compareTo SUMMER: " + Season.SPRING.compareTo(Season.SUMMER));
System.out.println("values(): " + Arrays.toString(Season.values()));
}
}
enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
运行结果:
五、枚举类实现接口
1、枚举类实现接口
枚举类可以实现一个或多个接口。
interface GetDesc {
String getDesc();
}
interface GetName {
String getName();
}
enum Season implements GetDesc, GetName {
SPRING("春天", "温暖"),
SUMMER("夏天", "炎热"),
AUTUMN("秋天", "凉爽"),
WINTER("冬天", "寒冷");
private String name;
private String desc;
Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
@Override
public String getDesc() {
return desc;
}
@Override
public String getName() {
return name;
}
}
2、每个枚举常量都可以有自己的具体实现
实现接口后,枚举中的每个常量都可以提供具体的实现。
public class Example {
public static void main(String[] args) {
for (Color color : Color.values()) {
System.out.println(color.getDescription());
}
}
}
interface GetDescription {
String getDescription();
}
enum Color implements GetDescription {
RED {
@Override
public String getDescription() {
return "This is red.";
}
},
BLUE {
@Override
public String getDescription() {
return "This is blue.";
}
},
GREEN {
@Override
public String getDescription() {
return "This is green.";
}
}
}
运行结果:
六、枚举类的抽象方法
在Java的枚举类中,可以定义抽象方法,并在每个枚举常量中提供具体的实现。这种特性使得枚举类不仅仅是常量的集合,还能够表现出多态性,从而为每个枚举常量定义不同的行为。
在枚举类中,可以声明一个或多个抽象方法。这些方法没有具体的实现,要求每个枚举常量必须提供自己的实现。
public class Example {
public static void main(String[] args) {
for (Color color : Color.values()) {
System.out.println(color.getDescription());
}
}
}
enum Color {
RED {
@Override
public String getDescription() {
return "This is red.";
}
},
BLUE{
@Override
public String getDescription() {
return "This is blue.";
}
},
GREEN{
@Override
public String getDescription() {
return "This is green.";
}
};
public abstract String getDescription();
}
运行结果:
七、在 switch 语句中使用枚举
在 switch 语句中可以使用枚举。
enum Color {
RED, GREEN, BLUE;
public String getColorName() {
return this.name();
}
}
public class ColorTest {
public static void main(String[] args) {
Color color = Color.GREEN;
switch (color) {
case RED:
System.out.println(color.getColorName() + " is selected.");
break;
case GREEN:
System.out.println(color.getColorName() + " is selected.");
break;
case BLUE:
System.out.println(color.getColorName() + " is selected.");
break;
}
}
}
需要注意的是,case 关键字后面的枚举常量需要直接写枚举常量的名字,不需要再加上类名。
如果像这样写:
case Color.BLUE:
编译器就会报错。
作者:Stewie Lee