玩转枚举Enum


placeholder image
admin 发布于:2017-11-06 16:17:34
阅读:loading

背景介绍

       昨天无意中看到一个同学写了一篇关于枚举类的文章,点击去看了一下感觉还是太年轻,但是嘴上说别人的写的不好那得拿出来好的东西(自己觉得)出来,所以就有了今天这篇文章。

       什么是枚举这个问题太抽象,我们来说说为什么要使用到枚举?其实我一直推荐别人使用Enum枚举类,也分别给周围人介绍过它的应用,今天再来唠一次,以后再有机会直接就给别人看这篇文章了,也省的我再唠了。如果你对代码有严重的洁癖,如果你想让你的代码变得易于维护、拓展新强,那么把枚举用到家你值得拥有,顺便说一下入门这块砖得感谢他杰哥。
为什么要使用枚举呢,我将通过枚举模拟一些使用场景来说明:
枚举类也是一个普通类,可以定义一些变量、常量,同样可以定义构造方法可以实现接口,可以以main函数作为测试类等等,但是它不能继承一个类、不能有非private和默认类型的构造函数、不能缺少实例定义,如果不定义则需要在第一行代码前使用“
;”,在最后一个实例后同样需要“;”,而最后一个实例结束时也可以多一个“,”,另外它不允许类进行new关键字构造,也就是说限定了它的实例化个数。
(1)基本定义、定义一个空枚举类,什么也没干(默认有无参的构造函数),相当于没有任何意义,参考如下:

public enum EnumSex {

   ;

} 
(2)实例定义、定义含有3个实例的枚举类,通过它可知这个类只能有3个实例化对象, 然而没有具体的可以使用的参数值,也相当于没有意义,跟一个Color类意义定义三原色红黄蓝一样,除了看起来够直观,仍然没有什么用途,参考如下:

public enum EnumSex {

   BOY,

   GIRL,

   PRIVATE,

   ;

}
(3)实例参数、定义含有3种实例的枚举类,并且定义每个实例对象的code和text,当常量类来使用(它的code与text就互相关联了),参考如下:

public enum EnumSex {

   BOY(1 , ""),

   GIRL(2 , ""),

   PRIVATE(3 , "保密"),

   ;

   private EnumSex(Integer key ,String text){

   }

} 

 如果我们熟悉枚举类的API,我们知道现在还是无法直接调用到它的具体属性(因为它目前还没有熟悉定义),而两个参数的构造函数必须有,因为上面的实例表示它应该有Integer和String类型的参数一样,下面我们继续给出属性和属性方法,参考如下所示:

public enum EnumSex {

   BOY(1 , ""),

   GIRL(2 , ""),

   PRIVATE(3 , "保密"),

   ;

   private Integer key;

   private String text;

   private EnumSex(Integer key ,String text){

      this.key = key;

      this.text = text;

   }

   public Integer getKey() {

      return key;

   }

   public String getText() {

      return text;

   }

} 
是否你已经发现上面的代码没有set函数,实际上根据我的经验基本较少的使用到set函数,故不给出了,构造函数的属性赋值与普通class一致,而使用时无需我们构造这个类,直接根据这个类具体的实例进行使用,这里我们有必要开始说一说这个Enum类怎么来使用了,参考如下图所示:

image.png

然而在实际应用中光上述3中使用还是不够的,实际情况往往是一个key去找寻一个对象,并根据对象去做一些事情,而不会直接通过实例名去做,而且实例名定义没有要求,key反而一般不会去改动,故上述的枚举类定义中还需要定义一个根据key获取实例的方法,实现方式就是使用Enum.values[]函数来做一次循环查找,所以,一个完整的枚举定义参考如下:

public enum EnumSex{

   BOY(1 , ""),

   GIRL(2 , ""),

   PRIVATE(3 , "保密"),

   ;

   private Integer key;

   private String text;

   private EnumSex(Integer key ,String text){

      this.key = key;

      this.text = text;

   }

   /**

    * 根据key获取性别对象

    */

   public static EnumSexgetSexByKey(Integer key){

      EnumSexenumSex = null;

      EnumSexsexs[] = EnumSex.values();

      for (EnumSex sex : sexs) {

         if(sex.getKey().equals(key)){

            enumSex= sex;

            break;

         }

      }

      return enumSex;

   }

   public Integer getKey() {

      return key;

   }

   public String getText() {

      return text;

   }

}

(4)循环显示、前台要显示性别的下拉框选项或列表中要显示性别字段,可使用循环判断的方式来实现,比如在新增页面我们要将所有的性别项呈现给用于进行下拉选择,我们无需在页面上“写死”,

后台代码参考如下: 

request.setAttribute("enumSexs",EnumSex.values());

前台页面代码参考如下: 

<select>

<c:forEach var="sex" items="${enumSexs }">

    <option value="${sex.key }" >${sex.text }</option>

</c:forEach>

</select>


前台要根据某个值进行逻辑判断,逻辑判断可以是jsp的也可以是js的,总之我们避免在页面上使用某个字符串值的比较的方式进行判断的逻辑,常见的举下列两种方式,参考如下:

<c:if test="${item.text eq '' }">

<!-- TODO 一些事情 -->

</c:if>

......

<script>

if(item.key == "1" || item.text == ""){

//TODO 一些事情

}

</script> 

如果有这种需求的话,使用枚举类的解决方案是在后台或者初始化页面时将需要用到的常量属性保存在request范围内,前台页面则直接使用属性变量去做判断,同样我们无需“写死”,

后台代码参考如下:

request.setAttribute("sexBoy",EnumSex.BOY);
前台代码参考如下:

<c:if test="${sexBoy.key == item.key || sexBoy.text eq item.text}">

<!-- TODO 一些事情 -->

</c:if> 

(5)定义接口与实现、高级应用之实现接口,如果我们枚举类的实例均要去做一件(或多件)事情,往往是根据自身的不同做的事情也不一样,好比我们这里的性别类枚举,这个类型好像不太好列举做什么事情,那就随便举一个场景来说明,比如要根据用户的性别来推荐一些不同类型的兴趣爱好,那么这个程序考虑使用枚举实现,参考如下代码:
兴趣爱好的接口类定义,它定义了一个根据用户性别key来获取对应的兴趣爱好列表,参考如下:

interface Interest {

   /**

    * 根据用户的性别获取相关的兴趣爱好信息

    */

   String[] getInterestBySex();

}
枚举类来实现这个接口,并且判断当前是不同的实例获取到对应的数据,参考如下代码:

public enum EnumSex implements Interest {

   BOY(1 , ""),

   GIRL(2 , ""),

   PRIVATE(3 , "保密"),

   ;

   private Integer key;

   private String text;

   private EnumSex(Integer key ,String text){

      this.key = key;

      this.text = text;

   }

   @Override

   public String[] getInterestBySex() {

      String values[] = null;

      if (this.equals(BOY)){

         values = new String[]{"打游戏" , "喝茶"};

      }else if(this.equals(GIRL)){

         values = new String[]{"看电影" , "聊天"};

      }else if(this.equals(PRIVATE)){

         //以上4中都有可能

         values = new String[]{"喝茶" , "聊天"};

      }

      return values;

   }

   /**

    * 根据key获取性别对象

    */

   public static EnumSexgetSexByKey(Integer key){

      EnumSexenumSex = null;

      EnumSexsexs[] = EnumSex.values();

      for (EnumSex sex : sexs) {

         if(sex.getKey().equals(key)){

            enumSex= sex;

            break;

         }

      }

      return enumSex;

   }

   public Integer getKey() {

      return key;

   }

   public String getText() {

      return text;

   }

}

上面代码中的@Override函数为各个实例的实现,可如果是这样我觉得这个枚举的写法白瞎了50%的作用,因为我们这里使用它来替换的逻辑就是重复繁多且又有相同逻辑的事情的,在说最终的写法之前需要注意的是,枚举类中实现的@Override为它的各个实例中默认全部都会调用的方法,我们可以将一些含有共同逻辑的方法或者方法多个实例类型有共同实现的逻辑放置在这个方法中,而没有共同实现的逻辑时则各个实现,换句话说在上面的实现方法中只去做多个实例间存在共有逻辑的实现,个性化的实现则由各个实例自己去实现,参考如下代码:

import java.util.ArrayList;

import java.util.List;

import java.util.Random;

public enum EnumSex implements Interest {

   BOY(1 , ""){

      @Override

      public String[] getInterestBySex() {

         return new String[]{"打游戏" , "喝茶"};

      }

   },

   GIRL(2 , ""){

      @Override

      public String[] getInterestBySex() {

         return new String[]{"看电影" , "聊天"};

      }

   },

   PRIVATE(3 , "保密"),//--不具体去实现,使用默认的方法实现

   ;

   private Integer key;

   private String text;

  

   private EnumSex(Integer key ,String text){

      this.key = key;

      this.text = text;

   }

   @Override

   public String[]getInterestBySex() {

      List<String> dataList = newArrayList<String>();

      EnumSex sexs[] = EnumSex.values();

      for (EnumSex sex : sexs) {

         if(sex.equals(PRIVATE)){

            continue;

         }

         String results[] = sex.getInterestBySex();

         //从每一个类型中随机获取一个元素值作为保密类型的爱好,这样保密类型就拥有各个枚举实例的爱好

         String value = results[newRandom().nextInt(results.length)];

         dataList.add(value);

      }

      String values[] = newString[dataList.size()];

      return dataList.toArray(values);

   }

   /**

    * 根据key获取性别对象

    */

   public static EnumSexgetSexByKey(Integer key){

      EnumSexenumSex = null;

      EnumSexsexs[] = EnumSex.values();

      for (EnumSex sex : sexs) {

         if(sex.getKey().equals(key)){

            enumSex= sex;

            break;

         }

      }

      return enumSex;

   }

   public Integer getKey() {

      return key;

   }

   public String getText() {

      return text;

   }

}

interface Interest {

   /**

    * 根据用户的性别获取相关的兴趣爱好信息

    */

   String[] getInterestBySex();

}

/**

 * 测试类

 */ 
 
public class Test {

   public static void main(String[] args) {

      //假设前台传递了一个3(保密)类型的性别过来,那么对应它的兴趣爱好如下(实现为各个实例中任取一个):

      Integer key = 3;

      //根据类型获取它对应的实例,并且调用实例对应的兴趣爱好

      String values[] = EnumSex.getSexByKey(key).getInterestBySex();

      for (String value : values){

         System.out.print(value + ",");

      }

   }

}
输出结果为:

打游戏,聊天, 

(6)扩展、高级应用之业务扩展,假设随着时代的发展,又出现一些新型的性别被普及,此时我们的程序中关于性别的爱好模块是需要更改的,但仅仅只是影响到了以下两处:

①需要添加一个新型的枚举类型;

②依照接口实现的规范去实现它的兴趣爱好;

如此即可,无需再更改其他的逻辑,真正意义上的解耦合。

OK,不知道你有没有注意到整个实现代码中没有一个使用到if-else if-else来控制逻辑,并且没有任何一处使用到了字符串变量、常量(魔鬼数字)去做判断,整个代码给人的感觉非常爽,另外在实现接口方式时跟class类实现完全一致,上述代码中的默认实现与单个实现的原理属于面向对象了;还有这种写法是不是就是以前的工厂和适配器模式呢~~如果后期我们需要改动,其实优势就很明显了,比如(1)我们要把“保密”改成“未知”(或者改成国际化显示sex.private.text),那么所有的地方均无需改动,比如前台页面判断和后台判断,因为我认为如果真的使用到了“保密”你肯定也是会有EnumSex.PRIVATE.getText()来代替的;比如(2)比如我们要再添加一种性别类型,那么只需要添加一个实例对象,并且重写它的兴趣取数逻辑即可,其他涉及到的代码一概不用动,值得说的是实际情况是已经没有这种新性别了,只是举例一下。 

 点赞


 发表评论

当前回复:作者

 评论列表


留言区