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、注意

  1. 枚举类中定义的枚举常量必须放到类体的非注释的第一行,如果有多个枚举常量,则使用逗号将它们分隔开,有多个枚举常量则可以在逗号处换行,就像数组的语法。
  2. 如果一个枚举类向上面的例子一样只定义了一系列枚举常量,没有其他的成员,这样最后可以不加分号,如果后面有其他的成员,则需要加分号。
  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 {};

这个静态代码块是用于初始化枚举常量的。编译器会在这个静态代码块中创建 REDGREEN 和 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

    物联沃分享整理
    物联沃-IOTWORD物联网 » Java枚举类详解与最佳实践

    发表回复