枚举Enum类还能实现接口!教你玩转枚举

枚举是什么?

枚举是一种特殊的数据类型,预先定义一组常量(对象),并且必须为其赋值。

Java 枚举类型的基本想法非常简单:

这些类通过共有的静态final域为每个枚举常量导出一个实例。枚举类型没有可以访问的构造器,所以它是真的final类。客户端不能创建枚举类型的实例,也不能对它进行扩展,因此不存实例,而只存在声明过程的枚举常量。也就是枚举类型是实例受控的。它们是单例(Singleton)的范型化,本质上是单元素的枚举。

枚举使代码更易拓展和不一定代码减少,对比见下:

我们使用1234用来表示一年中的四个季节

1、常规代码

public String getSeasonString(Integer key) {
         switch (key) {
             case 1:
                 return "SPRING";
             case 2:
                 return "SUMMER";
             case 3:
                 return "AUTUMN";
             case 4:
                 return "WINTER";
             default:
                 return null;
         }
     }

 

2、使用枚举

// 定义枚举类
 @JsonFormat(shape = JsonFormat.Shape.OBJECT)
 public enum SeasonTwoArgs {
 
     /**
      * 春天
      */
     SPRING(1, "春天"),
     SUMMER(2, "夏天"),
     AUTUMN(3, "秋天"),
     WINTER(4, "冬天");
 
     Integer key;
     String msg;
 
     SeasonTwoArgs(int key, String season) {
         this.key = key;
         this.msg = season;
 //        System.out.println("初始化:" + this.name() + "," + this.msg + "," + season);
     }
 
     // 很多情况,我们可能从前端拿到的值是枚举类的 key ,然后就可以通过以下静态方法获取到对应枚举值
     public static SeasonTwoArgs valueofKey(Integer key) {
         for (SeasonTwoArgs season : SeasonTwoArgs.values()) {
             if (season.key == key) {
                 return season;
             }
         }
         throw new IllegalArgumentException("No element matches " + key);
     }
 
     public String getMsg() {
         return msg;
     }
 
     public int getKey() {
         return key;
     }
 
     public void setKey(int key) {
         this.key = key;
     }
 
     public void setMsg(String msg) {
         this.msg = msg;
     }
     
     // 获取 季节的方法
     public String getSeasonString(Integer key) {
         return SeasonTwoArgs.valueofKey(key).msg;
     }

 

说了,在使用的时候,我都是调工具类里面的getSeasonString(String key)方法啊!

对比看来,发现代码并没有减少,反而增加了。

但是,如果后期发生了改变,使用枚举更加的方便,也不会影响到之前的业务逻辑。

枚举的要求:

  • 它们限制了列名变量可以采取的值。
  • 它们迫使您考虑列名可以采取的所有可能的值。
  • 它们是一个常数,而不是一个数字,增加了源代码的可读性

自定义多参数枚举

代码见上。

枚举类型保证了编译时的类型安全。包含同名常量的多个枚举类型可以在一个系统中和平共处。因为每个类型都有自己的命名空间。可以新增或者重新排列枚举类型中的常量,而无需重新编译它的客户端代码。

枚举 使用 == 的安全性

1、代码编译时期:

如果俩个枚举类型不同,编译就会报错。

         if (Season.SPRING.equals(TestSeason.AUTUMN)) ; // 编译正常
         if (Season.SPRING == TestSeason.AUTUMN) ;      // 编译失败,类型不匹配

2、运行时期:

Season season = null;
System.out.println(season == Season.SPRING);//正常运行
System.out.println(Season.SPRING.equals(season));//正常运行
System.out.println(season.equals(Season.SPRING));//空指针异常

 

switch中使用枚举

将枚举作为switch的参数

switch (season) {
             case SPRING: {
                 System.out.println(1);
                 return 1;
             }
             case SUMMER: {
                 System.out.println(2);
                 return 2;
             }
             case AUTUMN: {
                 System.out.println(3);
                 return 3;
             }
             case WINTER: {
                 System.out.println(4);
                 return 4;
             }
             default:
                 System.out.println(0);
                 return 0;
         }

 

如何输出JSON

1、自定义toJSONString方法

public String gtoJSONString() {
        JSONObject object = new JSONObject();
        object.put("name", this.getName());
        return object.toJSONString();
    }

 

2、借助jackson工具

1、类上面增加注解:
 @JsonFormat(shape = JsonFormat.Shape.OBJECT)
 public enum Season {
     
 2、get方法或者基本属性增加注解:
 @JsonProperty("name")
 public String getName() {
 return this.name();
 }

 

重写方法

/**
 * 枚举计算类
 */
public enum Operation {
    PLUS("+") {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    }, MINUS("-") {
        @Override
        public double apply(double x, double y) {
            return x - y;
        }
    }, TIMES("*") {
        @Override
        public double apply(double x, double y) {
            return x * y;
        }
    }, DIVIDE("/") {
        @Override
        public double apply(double x, double y) {
            return x / y;
        }
    };

    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    public abstract double apply(double x, double y);

    @Override
    public String toString() {
        return symbol;
    }

    public static void main(String[] args) {
        double x = 4;
        double y = 2;
        for (Operation operation : Operation.values()) {
            System.out.printf("%f %s %f = %f%n",
                    x, operation, y, operation.apply(x, y));
        }
    }
}

 

通过接口扩展枚举

虽然枚举类型是不可扩展的,但是接口类型确实可扩展的,它是用来表示API中的操作的接口类型。你可以定义另一个枚举类型,它实现这个接口,并用这个新类型的实例代替基本类型。

定义接口

public interface IOperation {
    double apply(double x, double y);
}

 

枚举实现:

/**
  * 枚举计算类
  */
 public enum Operation implements IOperation{
     PLUS("+") {
         @Override
         public double apply(double x, double y) {
             return x + y;
         }
     }, MINUS("-") {
         @Override
         public double apply(double x, double y) {
             return x - y;
         }
     }, TIMES("*") {
         @Override
         public double apply(double x, double y) {
             return x * y;
         }
     }, DIVIDE("/") {
         @Override
         public double apply(double x, double y) {
             return x / y;
         }
     };
 
     private final String symbol;
 
     Operation(String symbol) {
         this.symbol = symbol;
     }
     @Override
     public String toString() {
         return symbol;
     }
     public static void main(String[] args) {
         double x = 4;
         double y = 2;
         for (Operation operation : Operation.values()) {
             System.out.printf("%f %s %f = %f%n",
                     x, operation, y, operation.apply(x, y));
         }
 
     }
 }

 

扩展实现乘积运算:

public enum ExtOperation implements IOperation {
 
     EXP("^") {
         @Override
         public double apply(double x, double y) {
             return Math.pow(x, y);
         }
     };
     private final String symbol;
 
     ExtOperation(String symbol) {
         this.symbol = symbol;
     }
 
     @Override
     public String toString() {
         return this.symbol;
     }
     
     //入参实现IOperation接口并且是枚举类型。这个可以将该限定去掉,只要实现IOperation接口即可。
     private static <T extends Enum<T> & IOperation> void test(Class<T> tClass, double x,
                                                               double y) {
         for (IOperation operation : tClass.getEnumConstants()) {
             System.out.printf("%f %s %f = %f%n",
                     x, operation, y, operation.apply(x, y));
         }
     }
 
     public static void main(String[] args) {
         test(ExtOperation.class,2,3);//使用扩展实现枚举对象
         test(Operation.class,2,3);//使用默认的实现
     }
 
 }

 

可见:

枚举类型可以添加任意的方法和域,并实现任意的接口。它们提供了所有的Object方法的高级实现,实现了Comparable和Serializable接口,并针对枚举类型的可任意改变性提供了序列化方法。

EnumSet和EnumMap

1、EnumSet

EnumSet 是一种专门为枚举类型所设计的 Set 类型。

与HashSet相比,由于使用了内部位向量表示,因此它是特定 Enum 常量集的非常有效且紧凑的表示形式。

EnumSet 是抽象类,其有两个实现:RegularEnumSet 、JumboEnumSet,选择哪一个取决于实例化时枚举中常量的数量。但是 RegularEnumSet 和JumboEnumSet无法实例化,访问访问不是public

@Test
     public void testEnumSet() {
         // 使用场景 可用于 一批数据中,计算数据处于某种状态的有多少条/是否包含等
         final EnumSet<Season> enumSet = EnumSet.of(Season.SPRING, Season.WINTER);
         System.out.println("enumSet = " + enumSet);
         enumSet.add(Season.SUMMER);
         System.out.println("enumSet = " + enumSet);
 
         System.out.println("enumSet.contains(Season.SPRING) = " + enumSet.contains(Season.SPRING));
     }

 

2、EnumMap

EnumMap是一个专门化的映射实现,用于将枚举常量用作键。与对应的 HashMap 相比,它是一个高效紧凑的实现,并且在内部表示为一个数组:

@Test
     public void testEnumMap() {
         //EnumMap是一个专门化的映射实现,用于将枚举常量用作键。
         final EnumMap<Season, List<Person>> enumMap = new EnumMap<>(Season.class);
 
         // 写一个投票的场景, 模拟10个人 回答自己喜欢的季节
         for (int i = 0; i < 10; i++) {
             List<Person> personList = null;
             Season season = null;
             if (i < 3) {
                 season = Season.SPRING;
             } else if (i < 4) {
                 season = Season.SUMMER;
             } else if (i < 5) {
                 season = Season.AUTUMN;
             } else {
                 season = Season.WINTER;
             }
             if (enumMap.containsKey(season)) {
                 personList = enumMap.get(season);
             } else {
                 personList = new ArrayList<>();
             }
             personList.add(new Person("name" + i, String.valueOf(i % 2), i + 18));
             enumMap.put(season, personList);
         }
         enumMap.forEach((k, v) -> {
             System.out.println("喜欢" + k + "有" + v.size() + "个");
         });
         // 输出:
          /**
          * 喜欢SPRING有3个
          * 喜欢SUMMER有1个
          * 喜欢AUTUMN有1个
          * 喜欢WINTER有5个
          */
     }

 

关于实现单例模式,天生就是单例模式!