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