十三、枚举

文章目录

  • 枚举
    • 4.1 基础
    • 4.2 实现枚举
    • 4.3 自定义枚举

枚举

本文为书籍《Java编程的逻辑》1和《剑指Java:核心原理与应用实践》2阅读笔记

枚举是一种特殊的数据,它的取值是有限的,是可以枚举出来的,比如一年有四季、一周有七天。

4.1 基础

定义和使用基本的枚举是比较简单的,我们来看个例子。为表示一年四季,我们定义一个枚举类型Season,包括四个季节:春、夏、秋、冬,代码如下:

package com.ieening.learnEnum;

public enum Season {
    SPRING, SUMMER, AUTOM, WINTER;
}

枚举使用enum这个关键字来定义,Season包括四个值,分别表示春、夏、秋和冬,值一般是大写的字母,多个值之间以逗号分隔。枚举类型可以定义为一个单独的文件,也可以定义在其他类内部。

可以这样使用Season

Season season = Season.SPRING

Season season声明了一个变量season,它的类型是Seasonseason = Season.SPRING将枚举值SPRING赋值给season变量。枚举变量的toString方法返回其字面值,所有枚举类型也都有一个name方法,返回值与toString一样。

    @Test
    public void testEnumToString() {
        assertEquals("SPRING", Season.SPRING.toString());
    }

    @Test
    public void testEnumName() {
        assertEquals("SPRING", Season.SPRING.name());
    }

输出都是SPRING。枚举变量可以使用equals==进行比较,结果是一样的,例如:

    @Test
    public void testEnumEquals() {
        Season summer = Season.SUMMER;
        assertTrue(summer == Season.SUMMER);
        assertTrue(summer.equals(Season.SUMMER));
        assertFalse(summer == Season.AUTOM);
    }

枚举值是有顺序的,可以比较大小。枚举类型都有一个方法int ordinal(),表示枚举值在声明时的顺序,从 0 0 0开始,例如,如下代码输出为 1 1 1​:

    @Test
    public void testEnumOrdinal() {
        assertTrue(0 == Season.SPRING.ordinal());
        assertTrue(1 == Season.SUMMER.ordinal());
        assertTrue(2 == Season.AUTOM.ordinal());
        assertTrue(3 == Season.WINTER.ordinal());
    }

另外,枚举类型都实现了Java API中的Comparable接口,都可以通过方法compareTo与其他枚举值进行比较。比较其实就是比较ordinal的大小,例如,如下代码:

    @Test
    public void testEnumComparable() {
        Season summer = Season.SUMMER;
        assertEquals(1, summer.compareTo(Season.SPRING));
        assertEquals(-1, summer.compareTo(Season.AUTOM));
    }

枚举变量可以用于和其他类型变量一样的地方,如方法参数、类变量、实例变量等。枚举还可以用于switch语句。在switch语句内部,枚举值不能带枚举类型前缀,例如,直接使用SPRING,不能使用Season.SPRING

枚举类型都有一个静态的valueOf(String)方法,可以返回字符串对应的枚举值;枚举类型也都有一个静态的values方法,返回一个包括所有枚举值的数组,顺序与声明时的顺序一致。

    @Test
    public void testEnumValueOf() {
        assertEquals(Season.SPRING, Season.valueOf("SPRING"));
    }

    @Test
    public void testEnumValues() {
        assertArrayEquals(new Season[] { Season.SPRING, Season.SUMMER, Season.AUTOM, Season.WINTER }, Season.values());
    }

枚举的好处体现在以下几方面。

  1. 定义枚举的语法更为简洁。
  2. 枚举更为安全。一个枚举类型的变量,它的值要么为null,要么为枚举值之一,不可能为其他值,但使用整型变量,它的值就没有办法强制,值可能就是无效的。
  3. 枚举类型自带很多便利方法(如valuesvalueOftoString等),易于使用。

4.2 实现枚举

枚举是怎么实现的呢?枚举类型实际上会被Java编译器转换为一个对应的类,这个类继承了Java API中的java.lang.Enum类。Enum类有nameordinal两个实例变量,在构造方法中需要传递,name()toString()ordinal()compareTo()、equals()方法都是由Enum类根据其实例变量nameordinal实现的。valuesvalueOf方法是编译器给每个枚举类型自动添加的。下面是Enum类代码。

public abstract class Enum<E extends Enum<E>> implements Constable, Comparable<E>, Serializable {
   private final String name;
   private final int ordinal;

   public final String name() {
      return this.name;
   }

   public final int ordinal() {
      return this.ordinal;
   }

   protected Enum(String var1, int var2) {
      this.name = var1;
      this.ordinal = var2;
   }

   public String toString() {
      return this.name;
   }

   public final boolean equals(Object var1) {
      return this == var1;
   }

   public final int hashCode() {
      return super.hashCode();
   }

   protected final Object clone() throws CloneNotSupportedException {
      throw new CloneNotSupportedException();
   }

   public final int compareTo(E var1) {
      if (this.getClass() != var1.getClass() && this.getDeclaringClass() != var1.getDeclaringClass()) {
         throw new ClassCastException();
      } else {
         return this.ordinal - var1.ordinal;
      }
   }

   public final Class<E> getDeclaringClass() {
      Class var1 = this.getClass();
      Class var2 = var1.getSuperclass();
      return var2 == Enum.class ? var1 : var2;
   }

   public final Optional<EnumDesc<E>> describeConstable() {
      return this.getDeclaringClass().describeConstable().map((var1) -> {
         return java.lang.Enum.EnumDesc.of(var1, this.name);
      });
   }

   public static <T extends Enum<T>> T valueOf(Class<T> var0, String var1) {
      Enum var2 = (Enum)var0.enumConstantDirectory().get(var1);
      if (var2 != null) {
         return var2;
      } else if (var1 == null) {
         throw new NullPointerException("Name is null");
      } else {
         throw new IllegalArgumentException("No enum constant " + var0.getCanonicalName() + "." + var1);
      }
   }

   protected final void finalize() {
   }

   private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
      throw new InvalidObjectException("can't deserialize enum");
   }

   private void readObjectNoData() throws ObjectStreamException {
      throw new InvalidObjectException("can't deserialize enum");
   }
}

4.3 自定义枚举

以上枚举用法是最简单的,实际中枚举经常会有关联的实例变量和方法。比如,衣服Size例子,每个枚举值可能有关联的缩写和中文名称,可能需要静态方法根据缩写返回对应的枚举值,代码如下:

public enum Size {
    SMALL("S", "小号"),
    MEDIUM("M", "中号"),
    LARGE("L", "大号");
    private String abbr;
    private String title;
    private Size(String abbr, String title){
        this.abbr = abbr;
        this.title = title;
    }
    public String getAbbr() {
        return abbr;
    }
    public String getTitle() {
        return title;
    }
    public static Size fromAbbr(String abbr){
        for(Size size : Size.values()){
            if(size.getAbbr().equals(abbr)){
                return size;
            }
        }
        return null;
    }
}

上述代码定义了两个实例变量abbrtitle,以及对应的get方法,分别表示缩写和中文名称;定义了一个私有构造方法,接受缩写和中文名称,每个枚举值在定义的时候都传递了对应的值;同时定义了一个静态方法fromAbbr,根据缩写返回对应的枚举值。需要说明的是,枚举值的定义需要放在最上面,枚举值写完之后,要以分号(;)结尾,然后才能写其他代码。

每个枚举值经常有一个关联的标识符(id),通常用int整数表示,使用整数可以节约存储空间,减少网络传输。一个自然的想法是使用枚举中自带的ordinal值,但ordinal值并不是一个好的选择。为什么呢?因为ordinal值会随着枚举值在定义中的位置变化而变化,但一般来说,我们希望id值和枚举值的关系保持不变,尤其是表示枚举值的id已经保存在了很多地方的时候。比如,上面的Size例子,Size.SMALLordinal值为 0 0 0,我们希望 0 0 0表示的就是Size.SMALL,但如果增加一个表示超小的值ⅩSMALL

public enum Size {
    XSMALL, SMALL, MEDIUM, LARGE
}

这时, 0 0 0就表示ⅩSMALL了。所以,一般是增加一个实例变量表示id。使用实例变量的另一个好处是,id可以自己定义。比如,Size例子可以写为:

public enum Size {
    XSMALL(10), SMALL(20), MEDIUM(30), LARGE(40);
    private int id;
    private Size(int id){
        this.id = id;
    }
    public int getId() {
        return id;
    }
}

枚举还有一些高级用法,比如,每个枚举值可以有关联的类定义体,枚举类型可以声明抽象方法,每个枚举值中可以实现该方法,也可以重写枚举类型的其他方法。此外,枚举可以实现接口,也可以在接口中定义枚举。

package com.ieening.learnEnum;

public enum SeasonEnum {

    SPRING(1) {

        @Override
        public String description() {
            return "最是平常百姓家,新竹半掩几春花。门前犬吠鸣鸭远,老汉低眉啜现茶。";
        }

    },

    SUMMER(2) {
        @Override
        public String description() {
            return "树梢挑月伏南窗,梦随清风玉枕凉。了断新愁千缕结,池塘半锁藕花香。";
        }
    },

    AUTOM(3) {

        @Override
        public String description() {
            return "廊桥雁断树栖鸦,柳水秋枫揽碧霞。斜阳晚照无限好,诗茶伴雨写霜花。";
        }

    },

    WINTER(4) {
        @Override
        public String description() {
            return "玉蝶飘飞压翠枝,红梅初笑恨未迟。晨风落雪轻妙舞,月照西窗斌小诗。";
        }
    };

    private int id;

    public int getId() {
        return id;
    }

    private SeasonEnum(int id) {
        this.id = id;
    }

    public static SeasonEnum fromId(int id) {
        for (SeasonEnum seasonEnum : SeasonEnum.values()) {
            if (id == seasonEnum.id) {
                return seasonEnum;
            }
        }
        return null;
    }

    public abstract String description();
}


  1. 马俊昌.Java编程的逻辑[M].北京:机械工业出版社,2018. ↩︎

  2. 尚硅谷教育.剑指Java:核心原理与应用实践[M].北京:电子工业出版社,2023. ↩︎

你可能感兴趣的:(java系编程,java,开发语言)