一、认识枚举类型

  枚举类继承Enum类,该枚举类是final的故无法被继承。每个枚举值都是该枚举类的一个静态成员且成员的类型为该枚举类,每个枚举值都会在类的初始化阶段被实例化为该枚举类的一个对象,实例化时枚举类构造函数的第一个参数为枚举值的名称,第二个参数为枚举值的序号,从0开始编号。values()静态方法得到该枚举类的数组,数组中为所有的枚举值。valueOf()静态方法则是根据枚举值的名称得到枚举值

定义枚举类的关键字是 enum, 枚举类对象不能通过 new 出来,里面的 SPRING、SUMMER...这些其实就相当于是枚举类 Season 的实例。固定的就这几个,不能在外部创建新的实例。引用的时候直接类.实例名

枚举类型,其语法总让人觉着怪怪的,如下:

public enum Season { // 枚举类
    SPRING,SUMMER,AUTUMN,WINTER  // 枚举值
}

简单的一行,就定义了包含四个值的枚举类型,缺总让人觉着语法有点怪异。而在使用时:

public class Test {
    public static void main(String[] args) {
        System.out.println(Season.values()); // values方法得到枚举类数组
        System.out.println(Season.SPRING.name()); // name()方法获取枚举值的名称
        System.out.println(Season.SPRING.ordinal()); // ordinal()方法获取枚举值的序号
        System.out.println(Season.AUTUMN.name());
        System.out.println(Season.AUTUMN.ordinal());
    }
}

结果:

[Lcom.zwh.test.Season;@2f4d3709
SPRING
0
AUTUMN
2

那我们简单的一行定义中,到底发生了什么?枚举类型在编译时是怎样实现的?它还有着怎样的特点?

枚举产生之前

如果不使用枚举,我们要对“春夏秋冬”这四个值分别赋予一个数字,则常见的操作为:

public class Season {
    public static final int SPRING = 0;
    public static final int SUMMER = 1;
    public static final int AUTUMN = 2;
    public static final int WINTER = 3;
}

上述方法定义十分繁琐,而且容易出错。例如我们定义的int数字出现重复,编译器也不会给出任何的警示。同时,这样的操作是实在太频繁了,最终Java 5中增加了枚举类型。

而是用枚举类型后,一切就变成了如下所示的简单几行:

public enum Season {
    SPRING,SUMMER,AUTUMN,WINTER
}

而且,Java自动给按照枚举值出现的顺序,从0开始分配了编号通过name()可以获得枚举值的名称,通过ordinal()可以获得枚举值的编号

枚举实现原理

那我们定义枚举类型后,到底发生了什么呢?我们对枚举的实现原理进行探究。

首先,我们在实现Season枚举类时,并没有定义name()和ordinal()方法。

Enum抽象类

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
    private final String name;
 
    public final String name() {
        return name;
    }

    private final int ordinal;

    public final int ordinal() {
        return ordinal;
    }

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

并且,我们发现编译器不允许我们自行实现该抽象类从而构造一个新的类。但是,既然我们的Season枚举类可以调用其中的方法,因此Season枚举类应该是继承了该抽象类(Enum)

为了验证这一猜想,我们让Season类继承一个其他的类,发现果然不可以,因为Java是不允许多继承的。

具体,我们对Season类进行反编译,即:java -p Season.class,得到反编译后的结果:

D:\project\prism\myProject\springboot-tkMabatis\src\main\java\com\zwh\test>javap Season.class
Compiled from "Season.java"
public final class com.zwh.test.Season extends java.lang.Enum<com.zwh.test.Season> {
  public static final com.zwh.test.Season SPRING;
  public static final com.zwh.test.Season SUMMER;
  public static final com.zwh.test.Season AUTUMN;
  public static final com.zwh.test.Season WINTER;
  public static com.zwh.test.Season[] values();
  public static com.zwh.test.Season valueOf(java.lang.String);
  static {}; // 静态代码块的作用是给类变量进行初始化赋值
}

我们看到,对与枚举类,有很多值的注意的点:

  • 枚举类在经过编译后确实是生成了一个扩展了java.lang.Enum的类
  • 枚举类是final的,因此我们无法再继承它了
  • 我们定义的每个枚举值都是该类中的一个成员,且成员的类型仍然是Season类型
  • 枚举类中被默认增加了许多静态方法,例如values()等

为了进一步了解每个方法中的操作,我们使用javap -v Season.class > Season.txt每个方法中的字节码:

{
  public static final com.zwh.test.Season SPRING;
    descriptor: Lcom/zwh/test/Season;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final com.zwh.test.Season SUMMER;
    descriptor: Lcom/zwh/test/Season;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final com.zwh.test.Season AUTUMN;
    descriptor: Lcom/zwh/test/Season;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final com.zwh.test.Season WINTER;
    descriptor: Lcom/zwh/test/Season;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static com.zwh.test.Season[] values();
    descriptor: ()[Lcom/zwh/test/Season;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #1                  // Field $VALUES:[Lcom/zwh/test/Season;
         3: invokevirtual #2                  // Method "[Lcom/zwh/test/Season;".clone:()Ljava/lang/Object;
         6: checkcast     #3                  // class "[Lcom/zwh/test/Season;"
         9: areturn
      LineNumberTable:
        line 8: 0

  public static com.zwh.test.Season valueOf(java.lang.String);
    descriptor: (Ljava/lang/String;)Lcom/zwh/test/Season;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #4                  // class com/zwh/test/Season
         2: aload_0
         3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         6: checkcast     #4                  // class com/zwh/test/Season
         9: areturn
      LineNumberTable:
        line 8: 0

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=4, locals=0, args_size=0
         0: new           #4                  // class com/zwh/test/Season
         3: dup
         4: ldc           #7                  // String SPRING
         6: iconst_0                          // 将数字0压入到操作栈顶
         7: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        10: putstatic     #9                  // Field SPRING:Lcom/zwh/test/Season;
        13: new           #4                  // class com/zwh/test/Season
        16: dup
        17: ldc           #10                 // String SUMMER
        19: iconst_1                           // 将数字1压入到操作栈顶
        20: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        23: putstatic     #11                 // Field SUMMER:Lcom/zwh/test/Season;
        26: new           #4                  // class com/zwh/test/Season
        29: dup
        30: ldc           #12                 // String AUTUMN
        32: iconst_2                          // 将数字2压入到操作栈顶
        33: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        36: putstatic     #13                 // Field AUTUMN:Lcom/zwh/test/Season;
        39: new           #4                  // class com/zwh/test/Season
        42: dup
        43: ldc           #14                 // String WINTER
        45: iconst_3                           // 将数字3压入到操作栈顶
        46: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        49: putstatic     #15                 // Field WINTER:Lcom/zwh/test/Season;
        52: iconst_4
        53: anewarray     #4                  // class com/zwh/test/Season
        56: dup
        57: iconst_0
        58: getstatic     #9                  // Field SPRING:Lcom/zwh/test/Season;
        61: aastore
        62: dup
        63: iconst_1
        64: getstatic     #11                 // Field SUMMER:Lcom/zwh/test/Season;
        67: aastore
        68: dup
        69: iconst_2
        70: getstatic     #13                 // Field AUTUMN:Lcom/zwh/test/Season;
        73: aastore
        74: dup
        75: iconst_3
        76: getstatic     #15                 // Field WINTER:Lcom/zwh/test/Season;
        79: aastore                           // 将引用类型值存入数组
        80: putstatic     #1                  // Field $VALUES:[Lcom/zwh/test/Season;
        83: return
      LineNumberTable:
        line 9: 0
        line 8: 52
}

根据字节码,我们还原其操作代码,大致如下:

通过这里我们可以看到,在类的static操作中,编译器帮助我们生成每个枚举值的对象。

总结:

我们再总结一下,我们使用enum定义的枚举类型,会在编译之后转化为一个继承了java.lang.Enum的类,而我们定义的每个枚举值都会在类的初始化阶段被实例化为我们所定义的枚举类的一个对象

同时,编译器还帮我们在类中增加了两个方法,分别是:values()和valueOf()。

至此,我们对Java的枚举对象有了彻底的认识。

二、Java枚举类型(Enum)中的方法

  在枚举类被编译之后,有一些方法是编译器在编译阶段写入的,那这些方法有什么特点?枚举类中还有一些继承来的方法,它们又有哪些?枚举类中的枚举值是在编译阶段被创建为对象,那构造函数又在哪?(在Enum抽象类中)

1、Enum抽象类常见方法

我们上篇文章已经讲过,枚举类实际上继承了Enum抽象类,因此Enum抽象类是所有枚举类型的基本类,下面是它的常见方法:

  • ordinal()方法:该方法获取的是枚举变量在枚举类中声明的顺序,下标从0开始,如日期中的MONDAY在第一个位置,那么MONDAY的ordinal值就是0,如果MONDAY的声明位置发生变化,那么ordinal方法获取到的值也随之变化,注意在大多数情况下我们都不应该首先使用该方法,毕竟它总是变幻莫测的。
  • compareTo(E o)方法:则是比较枚举的大小,注意其内部实现是根据每个枚举的ordinal值大小进行比较的。
  • name()方法与toString():几乎是等同的,都是输出变量的字符串形式。
  • getDeclaringClass(): 返回该枚举变量所在的枚举类

需要再次说明的是,以上的方法都是Enum抽象类的方法,会被Enum的对象继承,而不是Enum的静态方法。而最终枚举值被实例化成了Enum对象,所以,枚举值拥有以上的方法。

这一块比较简单,我们直接举例子说明:

首先我们定义一个最简单的枚举类:

public enum Season {
    SPRING,SUMMER,AUTUMN,WINTER
}

之后我们再定义一个附带属性的枚举类

public enum StatusEnum {
    DOING("进行中","DOING"),
    DONE("已完成","DONE"),
    CANCELLED("已取消","CANCELLED");
    private String name;
    private String status;

    public String getName() {
        return name;
    }

    public String getStatus() {
        return status;
    }

    StatusEnum(String name, String status) {
        this.name = name;
        this.status = status;
    }
}

接下来,我们写方法进行试验:

public class Test2 {
    public static void main(String[] args) {
        Season[] seasons = Season.values();
        for (Season season : seasons) {
            System.out.println(season.toString());
            System.out.println(season.name());
            System.out.println(season.ordinal());
        }
        System.out.println("-----------");
        System.out.println(Season.valueOf("AUTUMN").name());
        System.out.println("-----------");
        Class<Season> aClass = Season.SUMMER.getDeclaringClass();
        System.out.println(aClass.getName());
        System.out.println("-----------");
        System.out.println(StatusEnum.CANCELLED.name());
        System.out.println(StatusEnum.CANCELLED.getName());
    }
}

结果:

SPRING
SPRING
0
SUMMER
SUMMER
1
AUTUMN
AUTUMN
2
WINTER
WINTER
3
-----------
AUTUMN
-----------
com.zwh.test.Season
-----------
CANCELLED
已取消

我们可以看到,对于每个枚举值,可以调用上述的继承自Enum抽象类的方法。

2、枚举类型的构造函数

既然枚举值是由编译器创建为枚举类型的实例,那它必然调用了构造函数。那该函数在哪呢?我们能不能调用呢?

其实该构造函数也在Enum抽象类中。

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

我们特意将方法注释也保留了下来,可以看到,该方法只能由编译器调用,开发人员无法调用。所以说,我们还是不要操心了,只需要定义好枚举类型,剩下的交给编译器。

  当枚举类自定义了无参构造函数和有参构造函数时,对于每一个无参的枚举值都会调用无参构造函数,而对于有参的枚举值调用有参构造函数,但是所有枚举值都会调用Emun抽象类的构造函数protected Enum(String name, int ordinal)。

public enum ColorEnum {
    RED,GREEN,BLUE; // 无参枚举值
    ColorEnum() {
        System.out.println("默认调用无参构造");
    }
}
public class Test4 {
    public static void main(String[] args) {
        ColorEnum blue = ColorEnum.BLUE;
        ColorEnum[] values = ColorEnum.values();
        for (ColorEnum value : values) {
            System.out.println(value.name() + ":" + value.ordinal());
        }
    }
}

结果如下:

默认调用无参构造
默认调用无参构造
默认调用无参构造
RED:0
GREEN:1
BLUE:2

再看下面的例子:

public enum ColorEnum {
//RED,GREEN,BLUE调用有参构造,YELLOW调用无参构造
//有属性方法时,记得在枚举实例序列的最后一个添加分号,代表枚举实例结束
RED("00","红色"),GREEN("01","绿色"),BLUE("02","蓝色"),YELLOW;
    private String code;
    private String name;
    private ColorEnum(){ // 无参构造
        System.out.println("默认调用无参构造");
    }
    private ColorEnum(String code, String name) { // 满参构造
        this.code = code;
        this.name = name;
    }
    public static String getName(String code) {
        //枚举在编译的时候才会插入一些方法,比如values()方法
        for(ColorEnum color:ColorEnum.values()) {
            if(color.getCode().equals(code)) {
                return color.name;
            }
        }
        return null;
    }
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class Test4 {
    public static void main(String[] args) {
        ColorEnum blue = ColorEnum.BLUE;
        ColorEnum[] values = ColorEnum.values();
        for (ColorEnum value : values) {
            System.out.println(value.name() + ":" + value.ordinal());
        }
    }
}

结果如下:

默认调用无参构造
RED:0
GREEN:1
BLUE:2
YELLOW:3

枚举类也有构造器,默认是 private 修饰的,并且也只能是 private。观察这段代码:

public enum Week {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; // 无参枚举值

    Week(){ // 由于枚举值都没有参数,故无参构造会被调用7次
        System.out.println("hello");
    }
}

public class Test {
    public static void main(String[] args) {
        Week w = Week.FRIDAY;
    }
}

你会发现这样的结果:

hello
hello
hello
hello
hello
hello
hello

构造函数共执行了 7 次,正好对应类中枚举项的数量。其实此类的枚举项的创建,就相当于调用无参构造器 new 出来的对象,也就是这个枚举类创建了7次实例,所以输出了7个 hello。

除了无参构造器,枚举类也有有参构造器。

public enum Week {
    SUNDAY(7), MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6);

    Week(int weekNum){
        System.out.println(weekNum);
    }

}

注意:枚举值中的参数与ordinal不一样。上述有参构造函数中打印的是参数值,而不是ordinal序号。

结果:

7
1
2
3
4
5
6

3、编译器插入的静态方法

每个枚举类都有两个 static 方法:

static Season[] values():返回本类所有枚举常量;
static Season valueOf(String name):通过枚举常量的名字返回Season常量,注意,这个方法与Enum类中的valueOf()方法的参数个数不同。

测试:

public enum Week {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
}
public class Test {
    public static void main(String[] args) {
        for (Week w : Week.values()) {
            System.out.println(w);
        }
        System.out.println("星期天:" + Week.valueOf("SUNDAY"));
    }
}

结果:

SUNDAY
MONDAY
TUESDAY
WEDNESDAY
THURSDAY
FRIDAY
SATURDAY
星期天:SUNDAY

我们知道values()方法和valueOf(String s)方法是由编译器插入到枚举类中的静态方法。这总让人觉得怪异。而同时,我们知道枚举类型中的每一个枚举值也在编译阶段被声明为了一个枚举类。

有人会这么认为:

  • 原来的Season枚举类中被编译器插入了values()方法和valueOf(String s)方法,因此能够正常调用Season.values()
  • 如果我们使用某个枚举值,如Season.AUTUMN向上转型成为Season枚举类,则无法调用values()方法和valueOf(String s)方法

其实这是不对的,因为无论是原生的Season枚举类还是Season.AUTUMN向上转型成的Season枚举类,本质上是同一个枚举类。因此,都应该可以调用values()方法和valueOf(String s)方法。

对此,我们进行验证:

public class Test3 {
    public static void main(String[] args) {
        System.out.println(Season.class.getName());
        System.out.println(Season.values()[1]);
        Season s = Season.AUTUMN;
        System.out.println(s.getClass().getName());
        System.out.println(s.values()[1]);
    }
}

结果:

com.zwh.test.Season
SUMMER
com.zwh.test.Season
SUMMER

通过该文章,我们对枚举类中的方法进行了全面的了解:

  • 枚举类在编译阶段会被编译器插入一些静态方法(values()和valueOf()方法)
  • 枚举类本身有个只有编译器能够调用的构造方法(Enum(String name, int ordinal)),编译器会使用该方法将枚举值实例化为枚举类型的对象
  • 枚举值被实例化(为Enum对象)后,继承了众多java.lang.Enum中的方法

三、枚举类的使用

1、扩展枚举值与自定义构造函数

之前,我们讨论枚举类时,主要是针对最简单的枚举类型。每个枚举值只有一个字符串,如:

public enum Season {
    SPRING,SUMMER,AUTUMN,WINTER
}

但是实际使用中,我们可能想给每个枚举值赋予更多的含义,例如,给每个季节一个中文说明和编码等。

即实现:

public enum Season {
    SPRING("春天",1201), // 对枚举值进行扩展,即加参数
    SUMMER("夏天",1202),
    AUTUMN("秋天",1203),
    WINTER("冬天",1204);
    private String name;
    private Integer code;

    public String getName() { 
        return name;
    }

    public Integer getCode() { // 注意该方法得到的是枚举值的参数值code,而不是ordinal
        return code;
    }

    Season(String name, Integer code) { // 自定义构造函数
        this.name = name;
        this.code = code;
    }

    @Override
    public String toString() {
        return "Season{" +
                "name='" + name + '\'' +
                ", code=" + code +
                '}';
    }
}

我们在枚举类中增加了name/code两个属性,并重新编写了构造方法。实现了我们的要求。

我们编写测试函数:

public class Test4 {
    public static void main(String[] args) {
        System.out.println(Season.AUTUMN.getName());
        System.out.println(Season.SPRING.toString()); // 输出变量的字符串形式
    }
}

结果:

秋天
SPRING

2、关于覆盖enum类方法

我们知道,枚举类最终继承了java.lang.Enum抽象类,那么我们能够覆盖java.lang.Enum抽象类中的方法么?

这个当然是可以的。其实在上面的代码中,笔者已经覆盖了java.lang.Enum抽象类中的toString()方法,并得出了自定义的输出。

3、枚举类中定义抽象方法

既然编译器最终将每个枚举值声明为枚举类的实例,那我们能在枚举类中声明抽象方法让枚举值去实现么?

听起来有些不可思议,其实也是可以的。我们在枚举类Season中声明了一个抽象方法sayHello()。然后在创建枚举值时,就必须实现该抽象方法。最终的代码如下:

public enum Season {
    SPRING("春天",1201){ // 实现枚举类的抽象方法
        @Override
        void sayHello() {
            System.out.println("hello,Spring");
        }
    },
    SUMMER("夏天",1202){
        @Override
        void sayHello() {
            System.out.println("hello,Summer");
        }
    },
    AUTUMN("秋天",1203){
        @Override
        void sayHello() {
            System.out.println("hello,Autumn");
        }
    },
    WINTER("冬天",1204){
        @Override
        void sayHello() {
            System.out.println("hello,Winter");
        }
    };
    private String name;
    private Integer code;

    public String getName() {
        return name;
    }

    public Integer getCode() {
        return code;
    }

    Season(String name, Integer code) {
        this.name = name;
        this.code = code;
    }

    @Override
    public String toString() {
        return "Season{" +
                "name='" + name + '\'' +
                ", code=" + code +
                '}';
    }
    abstract void sayHello(); // 枚举类中定义抽象方法
}

然后运行以下测试代码:

public class Test5 {
    public static void main(String[] args) {
        Season.AUTUMN.sayHello();
    }
}

结果:

hello,Autumn

我们知道在扩展了构造方法的情况下,我们可以为每个枚举值注入更多的属性。并且,枚举类作为java.lang.Enum抽象类的子类,可以重写父类的方法。同时,每个枚举值作为枚举对象的实例,可以实现枚举对象中定义的抽象方法。

4、枚举类成员变量和方法

枚举类和正常类一样,也可以有成员变量、实例方法、静态方法等。

public enum Week {
    SUNDAY(7), MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6);

    private int weekNum; // 成员变量

    Week(int weekNum){ // 有参构造
        this.weekNum = weekNum;
    }

    public int getWeekNum() { // 实例方法,该方法得到的是枚举值的参数
        return weekNum;
    }

}

使用:

public class Test {
    public static void main(String[] args) {
        Week w = Week.FRIDAY;
        System.out.println(w.getWeekNum());
    }
}

结果:5

5、类型约束

相信大家平时开发过程中,肯定这样定义过常量来使用:

public class Discount {
    public static final double EIGHT_DISCOUNT = 0.8;
    public static final double SIX_DISCOUNT = 0.6;
    public static final double FIVE_DISCOUNT = 0.5;
}

这样定义其实也没有什么问题,但是如果有一个方法是这样的:

BigDecimal calculatePrice(double discount){
    //...
}

需要传入商品折扣计算价格,使用上面的常量定义就没有类型上的约束,传入任何 double 类型的值都可以,编译器不会发出警告。单如果你使用枚举来定义这种情况,就会有更强的类型约束:

public enum Discount {
    EIGHT_DISCOUNT(0.8), SIX_DISCOUNT(0.6), FIVE_DISCOUNT(0.5);

    private double discountNum;

    Discount(double discountNum) {
        this.discountNum = discountNum;
    }

    double getDiscountNum(){
        return discountNum;
    }
}

使用:

public class Test {
    public static void main(String[] args) {
        calculatePrice(Discount.EIGHT_DISCOUNT);
    }

    static BigDecimal calculatePrice(Discount discount){
        System.out.println(discount.getDiscountNum());
        //...
        return null;
    }
}

结果:0.8

6、switch 中使用枚举值

public enum Week {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
}
public class Test {
    public static void main(String[] args) {
        Week w = Week.MONDAY; // 枚举值
        switch (w) {
            case MONDAY: // 枚举值
                System.out.println("周一");
                break;
            case TUESDAY:
                System.out.println("周二");
                break;
        }
    }
}

结果:周一

7、枚举类实现接口,消除 if/else

不知道从什么时候开始,Java 程序员总想着消除 if else。

基于此,我们用Java 中的枚举类型来消除部分 if else 吧。

我们创建的枚举类默认是被final修饰,并且默认继承了Enum类。因此不能再继承其他的类(Java只支持单继承,不支持多继承)。但是可以去实现接口。

有这样一个判断场景。

public class Test1 {
    public static void main(String[] args) {
        String animalType = "dog";
        if ("dog".equals(animalType)){
            System.out.println("吃骨头");
        } else if ("cat".equals(animalType)) {
            System.out.println("吃鱼干");
        } else if ("sheep".equals(animalType)) {
            System.out.println("吃草");
        }
    }
}

结果:吃骨头

怎样用枚举来消除掉 if/else 呐,看下面的代码:

先定义一个接口,里面有一个通用方法 eat()

public interface Eat {
    //
    String eat();
}

然后创建枚举类实现这个接口

public enum AnimalEnum implements Eat { // 实现Eat接口
    Dog(){ // 枚举值实现接口的方法
        @Override
        public void eat() {
            System.out.println("吃骨头");
        }
    },

    Cat() {
        @Override
        public void eat() {
            System.out.println("吃鱼干");
        }
    },

    Sheep() {
        @Override
        public void eat() {
            System.out.println("吃草");
        }
    }
}

调用的时候只需要一行代码:

public class Test1 {
    public static void main(String[] args) {
        String animalType = "Dog";
        AnimalEnum.valueOf(animalType).eat();
//        if ("dog".equals(animalType)){
//            System.out.println("吃骨头");
//        } else if ("cat".equals(animalType)) {
//            System.out.println("吃鱼干");
//        } else if ("sheep".equals(animalType)) {
//            System.out.println("吃草");
//        }
    }
}

结果:吃骨头

而且这样一来,以后假如我想扩充新的动物,只需要去枚举类中加代码即可,而不用改任何老代码,符合开闭原则

8、枚举值在单例模式中的应用

枚举在单例模式的一种实现方式中也可以用到。

public class SingletonExample {
    /**
     * 构造函数私有化,避免外部创建实例
     */
    private SingletonExample(){}

    private static SingletonExample getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton { // 枚举类型在编译的时候实例化枚举值的一个对象
        INSTANCE;
        private SingletonExample instance;

        // JVM 保证这个方法绝对只调用一次
        Singleton() { // 枚举值被实例化为一个Single对象
            instance = new SingletonExample(); // 由于只有一个枚举值,故只会创建一个SingletonExample对象
        }

        public SingletonExample getInstance() {
            return instance;
        }
    }
}

四、项目中使用枚举类

1、使用枚举来定义返回的code和msg

这样做的目的是好维护返回的信息,如果不使用枚举,而是自己随便写code和msg,后期维护起来很麻烦。

public enum ResultEnum {
    SUCCESS2("1", "请求成功"),
    FAILURE("0", "请求失败"),
    SUCCESS("200", "请求成功"),
    NO_LOGIN("301", "用户未登录或登录超时"),
    OFTEN("303", "请求频繁"),
    PARAM_ERROR("400", "参数错误"),
    INNER_ERROR("401", "内部错误"),
    NO_PRI("402", "无此权限"),
    WRONG_PASSWORD("403", "用户名或密码错误"),
    SERVER_ERROR("500", "服务器内部错误"),
    ILLEGAL_ARGUMENT("502", "不合法的参数"),
    TOKEN_ERROR("405", "token不正确");

    private String resultStatus;
    private String resultMessage;

    ResultEnum(String resultStatus, String resultMessage) {
        this.resultStatus = resultStatus;
        this.resultMessage = resultMessage;
    }

    public String getResultStatus() {
        return resultStatus;
    }

    public String getResultMessage() {
        return resultMessage;
    }
}

使用枚举类:将ResultEnum中的resultStatus和resultMessage取出后存入Result2中返回给前端

import lombok.Data;
import java.io.Serializable;

@Data
public class Result2 implements Serializable {
    private static final long serialVersionUID = 5576237395711742681L;

    private Object data = null;
    private String resultStatus;
    private String resultMessage;

    public static Result2 operate(ResultEnum resultEnum) {
        Result2 result = new Result2();
        result.setResultStatus(resultEnum.getResultStatus());
        result.setResultMessage(resultEnum.getResultMessage());
        return result;
    }

    public static Result2 operate(ResultEnum resultEnum, Object data) {
        Result2 result = new Result2();
        result.setResultStatus(resultEnum.getResultStatus());
        result.setResultMessage(resultEnum.getResultMessage());
        result.setData(data);
        return result;
    }

    public static Result2 operate(String resultStatus, String resultMessage) {
        Result2 result = new Result2();
        result.setResultStatus(resultStatus);
        result.setResultMessage(resultMessage);
        return result;
    }
}

测试:

public class demo7 {
    public static void main(String[] args) {
        System.out.println(Result2.operate(ResultEnum.PARAM_ERROR));
    }
}

结果:

Result2(data=null, resultStatus=400, resultMessage=参数错误)

2、在实体类中维护表字段存在的多个值

当数据库中字段status的值有多种情况时,如:状态 0-提示  1-预警 2-警告  -1处理完成

我们创建实体类是可以这样:除了定义整型的status字段外,再定义一个枚举类TypeStatus

@Data
@Table(name = "t_business_data")
public class BusinessData extends BaseEntity {
  ...

private Integer status;//状态 0-提示 1-预警 2-警告 -1处理完成

public enum TypeStatus {
PROMPT(0), // 注意枚举值的参数值就是该字段的值
WARNING(1),
CAVEAT(2),
COMPLETED(-1);
private Integer value;

public Integer getValue() {
return value;
}

TypeStatus(Integer value) {
this.value = value;
}
}

}

使用:

        if (b.getStatus() == BusinessData.TypeStatus.PROMPT.getValue()) {
                ...
            } else if (b.getStatus() == BusinessData.TypeStatus.WARNING.getValue()) {
                ...
            } else if (b.getStatus() == BusinessData.TypeStatus.CAVEAT.getValue()) {
                ...
            }

这样就不用将0,1,2这些数字写死在代码中,如果后期需要更改值,很难维护。

类似的做法还有:

@Datapublic class User extends BaseEntity {
/** * 0:普通用户 1:超级管理员 3: 公司管理员 */private Integer supper; public enum TypeSupper { COMMON(0), SUPERTUBE(1), COMPANY(3); private Integer value; public Integer getValue() { return value; } TypeSupper(Integer value) { this.value = value; } } }

 

posted on 2023-07-04 11:25  周文豪  阅读(549)  评论(0编辑  收藏  举报