Java枚举的使用方法详解

 Java枚举的使用方法详解

前言  你代码中的flag和status,都应该用枚举来替代

很多人都说,枚举在实际开发中很少用到,甚至就没用到。因为,他们的代码往往是这样子的:

public class Constant {
  /*
   * 以下几个变量表示英雄的状态
   */
  public final static int STATUS_WALKING = 0;//走
  public final static int STATUS_RUNNINGING = 1;//跑
  public final static int STATUS_ATTACKING = 2;//攻击
  public final static int STATUS_DEFENDING = 3;//防御
  public final static int STATUS_DEAD = 4;//挂了  

  /*
   * 以下几个变量表示英雄的等级
   */
  //此处略去N行代码
}

然后,他们是这样使用这个类的:

hero.setStatus(Contant.STATUS_ATTACKING);

嗯,然后他们就说,“我在实际开发中很少用到枚举”

当然,他们的意思是说很少用到枚举Enum这个类。

但是,我想说的是,上面这些代码,通通应该用Enum去实现。

为什么?

因为他们的代码完全建立在对队友的信任,假设来了个奇葩队友,做了这件事:

hero.setStatus(666);

你说,屏幕上的英雄会怎么样呢?

总之,假如你在实际编程中经常使用这样的代码,那是时候好好学习一下Enum了。

枚举初探   为什么要使用枚举类型

生活中处处都有枚举,包括“天然的枚举”,比如行星、一周的天数,也包括我们设计出来的枚举,比如csdn的tab标签,菜单等。

Java代码中表示枚举的方式,大体上有两种,一是int枚举,而是Enum枚举,当然,我们都知道,Enum枚举才是Java提供的真正枚举。

那么,为什么我们要使用Enum枚举类型呢?先来看看在Java 1.5之前,没有枚举类型时,我们是怎样表示枚举的。

以八大行星为例,每个行星对应一个int值,我们大概会这样写

public class PlanetWithoutEnum {
  public static final int PLANET_MERCURY = 0;
  public static final int PLANET_VENUS = 1;
  public static final int PLANET_EARTH = 2;
  public static final int PLANET_MARS = 3;
  public static final int PLANET_JUPITER = 4;
  public static final int PLANET_SATURN = 5;
  public static final int PLANET_URANUS = 6;
  public static final int PLANET_NEPTUNE = 7;
}

这种叫int枚举模式,当然你也可以使用String枚举模式,无论采用何种方式,这样的做法,在类型安全和使用方便性上都很差。
如果变量planet表示一个行星,使用者可以给这个值赋与一个不在我们枚举值里面的值,比如 planet = 9,这是哪个行星估计也只有天知道了; 再者,我们很难计算出到底有多少个行星,我们也很难对行星进行遍历操作等等。

现在我们用枚举来创建我们的行星。

public enum Planet {
  MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE;
}

上面这个是最简单的枚举,我们姑且叫做Planet 1.0,这个版本的行星枚举,我们实现了一个功能,就是任何一个Planet类型的变量,都可以由编译器来保证,传到给参数的任何非null对象一定属于这八个行星之一。

然后,我们对Planet进行升级,Java允许我们给枚举类型添加任意的方法,这里引言书中的代码,大家自行体会一下枚举的构造器、公共方法、枚举遍历等知识点。

public enum Planet {
  MERCURY(3.302e+23, 2.439e6),
  VENUS(4.869e+24, 6.052e6),
  EARTH(5.975e+24,6.378e6),
  MARS(6.419e+23, 3.393e6),
  JUPITER(1.899e+27, 7.149e7),
  SATURN(5.685e+26, 6.027e7),
  URANUS(8.683e+25, 2.556e7),
  NEPTUNE(1.024e+26,2.477e7);
  private final double mass; // In kilograms
  private final double radius; // In meters
  private final double surfaceGravity; // In m / s^2 

  // Universal gravitational constant in m^3 / kg s^2
  private static final double G = 6.67300E-11; 

  // Constructor
  Planet(double mass, double radius) {
    this.mass = mass;
    this.radius = radius;
    surfaceGravity = G * mass / (radius * radius);
  } 

  public double mass() {
    return mass;
  } 

  public double radius() {
    return radius;
  } 

  public double surfaceGravity() {
    return surfaceGravity;
  } 

  public double surfaceWeight(double mass) {
    return mass * surfaceGravity; // F = ma
  }
}
//注:这里对书中的代码做了微调
public class WeightTable {
  public static void main(String[] args) {
    printfWeightOnAllPlanets(8d);
  } 

  public static void printfWeightOnAllPlanets(double earthWeight) {
    double mass = earthWeight / Planet.EARTH.surfaceGravity();
    for (Planet p : Planet.values())
      System.out.printf("Weight on %s is %f%n", p, p.surfaceWeight(mass));
  }
}

运行WeightTable,打印结果如下:

Weight on MERCURY is 3.023254
Weight on VENUS is 7.240408
Weight on EARTH is 8.000000
Weight on MARS is 3.036832
Weight on JUPITER is 20.237436
Weight on SATURN is 8.524113
Weight on URANUS is 7.238844
Weight on NEPTUNE is 9.090108

在这个小程序里,我们用到了枚举的values()方法,这个方法返回了枚举类型里的枚举变量的集合,非常实用。

枚举进阶   计算器运算符枚举类

上一小节的例子里,我们用到了枚举类的公共方法,这一节,我们以计算器运算符 Operation 枚举类为例,看看怎么实现对于

每一个枚举对象,执行不同的操作。

首先,我们很容易想到的一个方法,在公共方法里,使用switch去判断枚举类型,然后执行不同的操作,代码如下:

public enum OperationUseSwitch {
  PLUS, MINUS, TIMES, DIVIDE; 

  double apply(double x, double y) {
    switch (this) {
      case PLUS:
        return x + y;
      case MINUS:
        return x + y;
      case TIMES:
        return x + y;
      case DIVIDE:
        return x + y;
    }
    // 如果this不属于上面四种操作符,抛出异常
    throw new AssertionError("Unknown operation: " + this);
  }
}

这段代码确实实现了我们的需求,但是有两个弊端。

首先是我们不得不在最后抛出异常或者在switch里加上default,不然无法编译通过,但是很明显,程序的分支是不会进入异常或者default的。

其次,这段代码非常脆弱,如果我们添加了新的操作类型,却忘了在switch里添加相应的处理逻辑,执行新的运算操作时,就会出现问题。

还好,Java枚举提供了一种功能,叫做 特定于常量的方法实现。

我们只需要在枚举类型中声明一个抽象方法,然后在各个枚举常量中去覆盖这个方法,实现如下:

public enum Operation {
  PLUS {
    double apply(double x, double y) {
      return x + y;
    }
  },
  MINUS {
    double apply(double x, double y) {
      return x - y;
    }
  },
  TIMES {
    double apply(double x, double y) {
      return x * y;
    }
  },
  DIVIDE {
    double apply(double x, double y) {
      return x / y;
    }
  }; 

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

这样,也就再也不会出现添加新操作符后忘记添加对应的处理逻辑的情况了,因为编译器就会提示我们必须覆盖apply方法。

不过,这种 特定于常量的方法实现 有一个缺点,那就是你很难在枚举常量之间共享代码。

我们以星期X的枚举为例,周一到周五是工作日,执行一种逻辑,周六周日,休息日,执行另一种逻辑。

如果还是使用 特定于常量的方法实现,写出来的代码可能就是这样的:

public enum DayUseAbstractMethod {
  MONDAY {
    @Override
    void apply() {
      dealWithWeekDays();//伪代码
    }
  },
  TUESDAY {
    @Override
    void apply() {
      dealWithWeekDays();//伪代码
    }
  },
  WEDNESDAY {
    @Override
    void apply() {
      dealWithWeekDays();//伪代码
    }
  },
  THURSDAY {
    @Override
    void apply() {
      dealWithWeekDays();//伪代码
    }
  },
  FRIDAY {
    @Override
    void apply() {
      dealWithWeekDays();//伪代码
    }
  },
  SATURDAY {
    @Override
    void apply() {
      dealWithWeekEnds();//伪代码
    }
  },
  SUNDAY {
    @Override
    void apply() {
      dealWithWeekEnds();//伪代码
    }
  }; 

  abstract void apply();
}

很明显,我们这段代码里面有相当多的重复代码。

那么要怎么优化呢,我们不妨这样想,星期一星期二等等是一种枚举,那么工作日和休息日,难道不也是一种枚举吗,我们能不能给Day的构造函数传入一个工作日休息日的DayType枚举呢?这也就是书中给出的一种叫策略枚举 的方法,代码如下:

public enum Day {
  MONDAY(DayType.WEEKDAY), TUESDAY(DayType.WEEKDAY), WEDNESDAY(
      DayType.WEEKDAY), THURSDAY(DayType.WEEKDAY), FRIDAY(DayType.WEEKDAY), SATURDAY(
      DayType.WEEKDAY), SUNDAY(DayType.WEEKDAY);
  private final DayType dayType; 

  Day(DayType daytype) {
    this.dayType = daytype;
  } 

  void apply() {
    dayType.apply();
  } 

  private enum DayType {
    WEEKDAY {
      @Override
      void apply() {
        System.out.println("hi, weekday");
      }
    },
    WEEKEND {
      @Override
      void apply() {
        System.out.println("hi, weekend");
      }
    };
    abstract void apply();
  }
}

通过策略枚举的方式,我们把Day的处理逻辑委托给了DayType,个中奥妙,读者可以细细体会。

枚举集合   EnumSet的使用

EnumSet提供了非常方便的方法来创建枚举集合,下面这段代码,感受一下

public class Text {
  public enum Style {
    BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
  } 

  // Any Set could be passed in, but EnumSet is clearly best
  public void applyStyles(Set<Style> styles) {
    // Body goes here
    for(Style style : styles){
      System.out.println(style);
    }
  } 

  // Sample use
  public static void main(String[] args) {
    Text text = new Text();
    text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
  }
}

这个例子里,我们使用了EnumSet.of方法,轻松创建了枚举集合。

枚举Map   EnumMap的使用

假设对于香草(Herb),有一个枚举属性Type(一年生、多年生、两年生)

Herb:

public class Herb {
  public enum Type { ANNUAL, PERENNIAL, BIENNIAL } 

  private final String name;
  private final Type type; 

  Herb(String name, Type type) {
    this.name = name;
    this.type = type;
  } 

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

现在,假设我们有一个Herb数组,我们需要对这个Herb数组按照Type进行分类存放。

所以接下来,我们需要创建一个Map,value肯定是Herb的集合了,那么用什么作为key呢?

有的人会使用枚举类型的ordinal()方法,这个函数返回int类型,表示枚举遍历在枚举类里的位置,这样做,缺点很明显,由于你的key的类型是int,不能保证传入的int一定能和枚举类里的变量对应上。

所以,在key的选择上,毫无疑问,只能使用枚举类型,也即Herb.Type。

最后还有一个问题,要使用什么Map? Java为枚举类型专门提供了一种Map,叫EnumMap,相比较与其他Map,这种Map在处理枚举类型上更快,有兴趣的同学可以研究一下这个map的内部实现。

下面让我们看看怎么使用EnumMap:

public static void main(String[] args) {
  Herb[] garden = { new Herb("Basil", Type.ANNUAL),
      new Herb("Carroway", Type.BIENNIAL),
      new Herb("Dill", Type.ANNUAL),
      new Herb("Lavendar", Type.PERENNIAL),
      new Herb("Parsley", Type.BIENNIAL),
      new Herb("Rosemary", Type.PERENNIAL) }; 

  // Using an EnumMap to associate data with an enum - Page 162
  Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>(
      Herb.Type.class);
  for (Herb.Type t : Herb.Type.values())
    herbsByType.put(t, new HashSet<Herb>());
  for (Herb h : garden)
    herbsByType.get(h.type).add(h);
  System.out.println(herbsByType); 

}

总结

和int枚举相比,Enum枚举的在类型安全和使用便利上的优势是不言而喻的。
Enum为枚举提供了丰富的功能,如文章中提到的特定于常量的方法实现和策略枚举。
EnumSet和EnumMap是两个为枚举而设计的集合,在实际开发中,用到枚举集合时,请优先考虑这两个。

如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • 三分钟快速掌握Java中枚举(enum)

    什么是枚举? 枚举是JDK5引入的新特性.在某些情况下,一个类的对象是固定的,就可以定义为枚举.在实际使用中,枚举类型也可以作为一种规范,保障程序参数安全.枚举有以下特点: Java中枚举和类.接口的级别相同. 枚举和类一样,都有自己的属性.方法.构造方法,不同点是:枚举的构造方法只能是private修饰,也就无法从外部构造对象.构造方法只在构造枚举值时调用. 使用enum关键字声明一个枚举类型时,就默认继承自Java中的 java.lang.Enum类,并实现了java.lang.Seriab

  • JAVA 枚举单例模式及源码分析的实例详解

    JAVA 枚举单例模式及源码分析的实例详解 单例模式的实现有很多种,网上也分析了如今实现单利模式最好用枚举,好处不外乎三点: 1.线程安全 2.不会因为序列化而产生新实例 3.防止反射攻击但是貌似没有一篇文章解释ENUM单例如何实现了上述三点,请高手解释一下这三点: 关于第一点线程安全,从反编译后的类源码中可以看出也是通过类加载机制保证的,应该是这样吧(解决) 关于第二点序列化问题,有一篇文章说枚举类自己实现了readResolve()方法,所以抗序列化,这个方法是当前类自己实现的(解决) 关于

  • Java枚举类型enum的详解及使用

     Java枚举类型enum的详解及使用 最近跟同事讨论问题的时候,突然同事提到我们为什么Java 中定义的常量值不采用enmu 枚举类型,而采用public final static 类型来定义呢?以前我们都是采用这种方式定义的,很少采用enum 定义,所以也都没有注意过,面对突入起来的问题,还真有点不太清楚为什么有这样的定义.既然不明白就抽时间研究下吧. Java 中的枚举类型采用关键字enum 来定义,从jdk1.5才有的新类型,所有的枚举类型都是继承自Enum 类型.要了解枚举类型,建议大

  • 详解Java中的 枚举与泛型

    详解Java中的 枚举与泛型 一:首先从枚举开始说起 枚举类型是JDK5.0的新特征.Sun引进了一个全新的关键字enum来定义一个枚举类.下面就是一个典型枚举类型的定义: public enum Color{ RED,BLUE,BLACK,YELLOW,GREEN } 显然,enum很像特殊的class,实际上enum声明定义的类型就是一个类. 而这些类都是类库中Enum类的子类(Java.lang.Enum).它们继承了这个Enum中的许多有用的方法.我们对代码编译之后发现,编译器将 enu

  • Java(enum)枚举用法详解

    概念 enum的全称为 enumeration, 是 JDK 1.5 中引入的新特性. 在Java中,被 enum 关键字修饰的类型就是枚举类型.形式如下: enum Color { RED, GREEN, BLUE } 如果枚举不添加任何方法,枚举值默认为从0开始的有序数值.以 Color 枚举类型举例,它的枚举常量依次为RED:0,GREEN:1,BLUE:2 枚举的好处:可以将常量组织起来,统一进行管理. 枚举的典型应用场景:错误码.状态机等. 枚举类型的本质 尽管enum 看起来像是一种

  • java仿枚举实例

    如下所示: enum Color{//枚举 RED,GREEN,YELLOW; } class Color1{//模仿枚举 private static int ordinal=-1; private Color1(){ ordinal++; } public static final Color1 RED=new Color1(); public static final Color1 GREEN=new Color1(); public static final Color1 YELLOW=

  • java枚举使用详细介绍及实现

    java枚举使用详解 在实际编程中,往往存在着这样的"数据集",它们的数值在程序中是稳定的,而且"数据集"中的元素是有限的. 例如星期一到星期日七个数据元素组成了一周的"数据集",春夏秋冬四个数据元素组成了四季的"数据集". 在java中如何更好的使用这些"数据集"呢?因此枚举便派上了用场,以下代码详细介绍了枚举的用法. package com.ljq.test; /** * 枚举用法详解 * * @aut

  • Java枚举的使用方法详解

     Java枚举的使用方法详解 前言  你代码中的flag和status,都应该用枚举来替代 很多人都说,枚举在实际开发中很少用到,甚至就没用到.因为,他们的代码往往是这样子的: public class Constant { /* * 以下几个变量表示英雄的状态 */ public final static int STATUS_WALKING = 0;//走 public final static int STATUS_RUNNINGING = 1;//跑 public final stati

  • java Future 接口使用方法详解

    java Future 接口使用方法详解 在Java中,如果需要设定代码执行的最长时间,即超时,可以用Java线程池ExecutorService类配合Future接口来实现. Future接口是Java标准API的一部分,在java.util.concurrent包中.Future接口是Java线程Future模式的实现,可以来进行异步计算. Future模式可以这样来描述:我有一个任务,提交给了Future,Future替我完成这个任务.期间我自己可以去做任何想做的事情.一段时间之后,我就便

  • Java String对象使用方法详解

    Java String对象使用方法详解 先来看一个例子,代码如下: public class Test { public static void main(String[] args) { String str = "abc"; String str1 = "abc"; String str2 = new String("abc"); System.out.println(str == str1); System.out.println(str1

  • Java螺旋矩阵处理方法详解

    题目描述: 给定一 m*n 的矩阵,请按照逆时针螺旋顺序,返回矩阵中所有元素. 示例: 思路: 这是一道典型的模拟问题: 我们可以分析一下,遍历前进轨迹: 向右 - > 向下 -> 向左 -> 向上 -> 向右 … 于是,我们可以在循环中模拟这样的前进轨迹,记录 右,下,上左,四个边界,每次拐弯时更新边界值,再进行下一次拐弯,循环往复,直至结束.结束条件为 左边界加一大于右边界,或者上边界加一大于下边界. 图解: 代码: class Solution { public List&l

  • Java实现定时任务的方法详解

    目录 前言 定时任务是什么 定时任务的有哪些是实现方式 纯手写单线程循环 Timer 和它的小伙伴 ScheduledExecutorService Spring 提供的定时任务 总结 前言 学过定时任务,但是我忘了,忘得一干二净,害怕,一直听别人说: 你写一个定时任务就好了. 写个定时任务让他去爬取就行了. 我不会,所以现在得补回来了,欠下的终究要还的,/(ㄒoㄒ)/~~ 定时任务是什么 大家都用过闹钟,闹钟可以说是一种定时任务. 比如我们设定了周一到周五早上7点半的时间响铃,那么闹钟就会在周

  • java获取类名的方法详解

    如果我们要获取当前运行的类名,怎么来获取? 在Class类中,有如下一个方法: 比如现在有一个类Demo7.java package pxx.test1; public class Demo7 { public static void main(String[] args) { Demo7 demo7 = new Demo7(); System.out.println(demo7.getClass().getName()); } } 运行结果: 上面就是直接这个对象调用了getClass()得到

  • Java的枚举类型使用方法详解

    1.背景 在java语言中还没有引入枚举类型之前,表示枚举类型的常用模式是声明一组具有int常量.之前我们通常利用public final static 方法定义的代码如下,分别用1 表示春天,2表示夏天,3表示秋天,4表示冬天. public class Season { public static final int SPRING = 1; public static final int SUMMER = 2; public static final int AUTUMN = 3; publ

  • Java文本编辑器实现方法详解

    本文实例讲述了Java文本编辑器实现方法.分享给大家供大家参考,具体如下: 结构分析: 界面布局 : EditFrame main方法所在: EditText 打开功能实现:  FileReadThread 保存跟能实现:  save 实际运行效果: 附:完整代码实现 一. EditFrame 包括一个菜单Menu 底部:日期时间 代码附上: public class EditFrame extends JFrame { // TODO 自动生成的构造函数存根 boolean saveFlag

  • Java JDBC基本使用方法详解

    本文实例讲述了Java JDBC基本使用方法.分享给大家供大家参考,具体如下: 本文内容: 什么是JDBC JDBC的使用 事务 连接池 DbUtils 首发日期:2018-05-27 修改: 2018-07-19:增加了事务.连接池.DBUtils 2018-07-27:对特别情况下的事务进行了描述.对DBUtils增加了关闭资源.关闭流.连接池发现漏了释放连接. 什么是JDBC: JDBC全称Java Database Connectivity JDBC可以通过载入不同的数据库的"驱动程序&

随机推荐