Java Enum

501 阅读5分钟

介绍

在Java中, 我们通过 static final 来定义常量。例如,我们定义周一到周日这7个常量,可以用7个不同的 int 表示

    public class Weekday {
        public static final int SUN = 0;
        public static final int MON = 1;
        public static final int TUE = 2;
        public static final int WED = 3;
        public static final int THU = 4;
        public static final int FRI = 5;
        public static final int SAT = 6;
    }

使用常量时,可以这么引用

    if(day == Weekday.SAT || day == Weekday.SUN) {
    	// TODO: 周末
    }

在 JDK 1.5 之前没有枚举类型,那时候一般用常量来替代。而使用 Java 枚举类型 enum 可以更贴近地表示这种常量。

    public enum Weekday {
      SUN, MON, TUE, WED, THU, FRI, SAT
    }

编译成 class 文件后反编译查看:

    // Compiled from "Weekday.java"
    public final class EnumTest.Weekday extends java.lang.Enum<EnumTest.Weekday> {
      public static final EnumTest.Weekday SUN;
      public static final EnumTest.Weekday MON;
      public static final EnumTest.Weekday TUE;
      public static final EnumTest.Weekday WED;
      public static final EnumTest.Weekday THU;
      public static final EnumTest.Weekday FRI;
      public static final EnumTest.Weekday SAT;
      public static EnumTest.Weekday[] values();
      public static EnumTest.Weekday valueOf(java.lang.String);
      static {};
    }

从反编译结果可知:

  1. 枚举类型的关键字 enum 其实只是一个语法糖,编译器最终把它转化为一个final类,因此枚举是不可继承的。
  2. 枚举类型都继承自 java.lang.Enum 类。
  3. 枚举的每一个取值被编译器传化为了一个个 static final 属性。
  4. 本质上,这就是一个普通类,因此你可以在枚举是添加各种方法,甚至是main方法。

神奇的 values() 方法

从上面我们可以看出枚举类型被添加了一个静态的 values() 方法,但是 java.lang.Enum 并没有该方法。其实,这个方法是编译器添加的。通过这个方法可以获取到该枚举类型的所有取值。这个方法在需要遍历枚举取值,进行判断筛选的场景非常有用

Enum类常用方法

Java枚举类使用详解

定义枚举

用 enum 关键字加上名称和大括号包含起来的枚举值体即可

  • 普通枚举类
    public enum Weekday {
      SUN, MON, TUE, WED, THU, FRI, SAT;
    }
  • 添加属性和方法
    public enum Weekday {  
    	SUN, MON, TUE, WED, THU, FRI, SAT;    
    	public final static int today = 0;    
    	public static int getDay() {    
    		return 0;  
    	}
    }
  • 带有构造器的枚举
    public enum Weekday {  
    	//通过括号赋值, 必须给定构造器
    	SUN(0), MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6);    
    	public Integer val;  
    	Weekday(int val) {    
    		this.val = val;  
    	}
    
    	public static void main(String[] args) {
        System.out.println(Weekday.THU.val); // 4
      }
    }

反编译后

可以发现,每个枚举值都是Weekday类型,SUN(0) <==> new Weekday(0)

同理,枚举值内后括号内可以有多个值,如下

    public enum ExamEnum {
        A(90,100,"优秀"),
        B(70,89,"良"),
        C(60,69,"及格"),
        D(0,59,"不及格");
    
        //最高分
        private int max;
        //最低分
        private int min;
        //评级
        private String assess;
    
        public String getAssess(){
            return assess;
        }
    
        ExamEnum(int min,int max,String assess){
            this.max = max;
            this.min = min;
            this.assess = assess;
        }
    }
  • 嵌套枚举类型

    我们可以在类,接口或另一个枚举类型中有一个嵌套的枚举类型声明。

    相当于静态内部类

  • 实现接口的枚举

    • 枚举类可以实现接口,并实现接口中的方法
    • 枚举值可以重写枚举类中的方法

使用枚举

在使用 Enum 时候有几个地方需要注意:

  1. enum 类型不支持 publicprotected 修饰符的构造方法,因此构造函数一定要是 private包级私有 的。也正因为如此,所以枚举对象是无法在程序中通过直接调用其构造方法来初始化的。
  2. 定义 enum 类型时候,如果是简单类型,那么最后一个枚举值后不用跟任何一个符号;但如果有定制方法,那么最后一个枚举值与后面代码要用分号';'隔开,不能用逗号或空格。
  3. 由于 enum 类型的值实际上是通过运行期构造出对象来表示的,所以在 cluster 环境下,每个虚拟机都会构造出一个同义的枚举对象。因而在做比较操作时候就需要注意,如果直接通过使用等号 ( ‘ == ’ ) 操作符,这些看似一样的枚举值一定不相等,因为这不是同一个对象实例。
  • switch case
    Weekday weekday = Weekday.SUN;
    switch (weekday) {
      case MON:
        System.out.println("星期一");
        break;
      case TUE:
        System.out.println("星期二");
        break;
      case WED:
        System.out.println("星期三");
        break;
      case THU:
        System.out.println("星期四");
        break;
      case FRI:
        System.out.println("星期五");
        break;
      case SAT:
        System.out.println("星期六");
        break;
      default:
        System.out.println("星期天");
        break;
    }
  • 遍历
    for (Weekday weekday : Weekday.values()) {
      System.out.println(weekday);
    }
  • 下标
    Weekday.MON.ordinal() // 1
  • 枚举默认实现了 Comparable接口
    Weekday.MON.compareTo(Weekday.SAT) // -5

Enum 相关工具类

JDK5.0 中在增加 Enum 类的同时,也增加了两个工具类 EnumSetEnumMap,这两个类都放在 java.util 包中。EnumSet 是一个针对枚举类型的高性能的 Set 接口实现。EnumSet 中装入的所有枚举对象都必须是同一种类型,在其内部,是通过 bit-vector 来实现,也就是通过一个 long 型数。EnumSet 支持在枚举类型的所有值的某个范围中进行迭代。回到上面日期枚举的例子上:

    enum WeekDayEnum { Mon, Tue, Wed, Thu, Fri, Sat, Sun }

你能够在每周七天日期中进行迭代,EnumSet 类提供一个静态方法 range 让迭代很容易完成:

    for(WeekDayEnum day : EnumSet.range(WeekDayEnum.Mon, WeekDayEnum.Fri)) { 
        System.out.println(day); 
    }

打印结果如下:

Mon 
Tue 
Wed 
Thu 
Fri

EnumSet 还提供了很多个类型安全的获取子集的 of 方法,使你很容易取得子集:

    EnumSet<WeekDayEnum> subset = EnumSet.of(WeekDayEnum.Mon, WeekDayEnum.Wed); 
         for (WeekDayEnum day : subset) { 
             System.out.println(day);  
         }

打印结果如下:

Mon 
Wed

EnumSet 类似,EnumMap 也是一个高性能的 Map 接口实现,用来管理使用枚举类型作为 keys 的映射表,内部是通过数组方式来实现。EnumMap 将丰富的和安全的 Map 接口与数组快速访问结合到一起,如果你希望要将一个枚举类型映射到一个值,你应该使用 EnumMap。看下面的例子:

    // 定义一个 EnumMap 对象,映射表主键是日期枚举类型,值是颜色枚举类型
    private static Map<WeekDayEnum, RainbowColor> schema = 
               new EnumMap<WeekDayEnum, RainbowColor>(WeekDayEnum.class); 
        
    static{ 
       // 将一周的每一天与彩虹的某一种色彩映射起来
       for (int i = 0; i < WeekDayEnum.values().length; i++) { 
           schema.put(WeekDayEnum.values()[i], RainbowColor.values()[i]); 
       } 
    } 
    System.out.println("What is the lucky color today?"); 
    System.out.println("It's " + schema.get(WeekDayEnum.Sat));

EnumSet 类的常用方法