于Java 1.5增加的enum type.
enum type是由一组固定的常量组成的类型,比如四个季节、扑克花色。
在出现enum type之前,通常用一组int常量表示枚举类型。
比如这样:
public static final int APPLE_FUJI = 0; public static final int APPLE_PIPPIN = 1; public static final int APPLE_GRANNY_SMITH = 2; public static final int ORANGE_NAVEL = 0; public static final int ORANGE_TEMPLE = 1; public static final int ORANGE_BLOOD = 2;
如果只是想用作枚举,感觉这样也没什么。
但如果把上面的苹果和橘子互作比较,或者写成....
int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN;
虽合法但诧异,这是在做果汁吗?
而且,这种常量是compile-time常量,编译后一切都结束了,使用这个常量的地方都被替换为该常量的值。
如果该常量值需要改变,所有使用该常量的代码都必须重新编译。
更糟糕的情况是,不重新编译也可以正常运行,只不过会得到无法预测的结果。
(ps:我觉得更遭的是有人直接把常量值写到代码里...)
另外,比如上面的APPLE_FUJI,我想打印它的名字,不是它的值。
不仅如此,我还想打印所有苹果,我想打印苹果一共有多少种类。
当然,如果想打印也可以,只是相比直接使用enum,无论怎么做都很麻烦。
如果使用enum,比如:
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH } public enum Orange { NAVEL, TEMPLE, BLOOD }
看起来就是一堆常量,但是enum没有实例,也没有可访问的构造器,无法对其进行扩展。
enum本身就是final,所以很多时候也直接用enum实现singleton。
enum在编译时是类型安全的,比如有地方声明了上面代码中的Apple类型的参数,那么被传到该参数的引用肯定是三种苹果之一。
而且enum本身就是一个类型,可以有自己的方法和field,而且可以实现接口。
附上书中太阳系enum,很难想象如果有类似需求时用普通常量来实现。
也许我可以声明一个Planet类,再给它加上field的方法,然后在一个constant类中声明为final
但这样却无法保证Planet类仅用作常量,所以还是用enum吧:
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 } }
然后我们就可以这样使用Planet enum,无论是值还是名字,使用起来都很自然:
public class WeightTable { public static void main(String[] args) { double earthWeight = Double.parseDouble(args[0]); double mass = earthWeight / Planet.EARTH.surfaceGravity(); for (Planet p : Planet.values()) System.out.printf("Weight on %s is %f%n",p, p.surfaceWeight(mass)); } }
其实像Planet这样的方式对多数使用枚举的场景而言足够了。
也就是说每个Planet常量表达的是不同的数据,但也有例外。
比如,我们要为enum中的每一个常量赋予不同的行为。
下面是书中用enum表达计算的例子:
import java.util.HashMap; import java.util.Map; 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; } }; private final String symbol; Operation(String symbol) { this.symbol = symbol; } @Override public String toString() { return symbol; } abstract double apply(double x, double y); private static final Map<String, Operation> stringToEnum = new HashMap<String, Operation>(); static { for (Operation op : values()) stringToEnum.put(op.toString(), op); } public static Operation fromString(String symbol) { return stringToEnum.get(symbol); } public static void main(String[] args) { double x = Double.parseDouble(args[0]); double y = Double.parseDouble(args[1]); for (Operation op : Operation.values()) System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); } }
对不同的枚举常量进行switch..case..其实也能表达出我们想要的效果。
如果以后增加了新的常量则需要再对应加上一个case,当然,不加也不会有任何提示,然后最坏的情况就是运行时出了问题。
如上面的代码是常量行为的正确使用方法,即constant-specific method implementation。
为行为提供一个抽象,并为每一个常量提供一个实现,即一个枚举常量也是constant-specific class body。
采用这种方式时,如果新增一个常量,则必须提供一个方法实现,否则编译器会给出提示,这就多了一层保障。
遗憾的是,这种方式也有缺陷。
比如我们有这样一个需求,计算某一天的薪水,这个某一天可以是一周中的某一天,也可能是某个节日,比如周一到周五使用相同的运算方式,周末另算,某节日另算。
也就是说我需要在枚举中声明代表周一到周日的常量,如果我继续使用之前的方式去声明一个抽象方法,如果周一到周五采用完全一样的计算,则会出现五段完全相同的代码。
但即使这样我们也不能用回switch..case..方式,增加一个常量时强制选择其选择一种行为实现是必须的。
于是我们有一种叫strategy enum的方式,即枚举中声明另外一个枚举的field,该field则代表策略,并提供策略相关的行为。
下面是书中代码:
enum PayrollDay { MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY( PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY( PayType.WEEKEND), SUNDAY(PayType.WEEKEND); private final PayType payType; PayrollDay(PayType payType) { this.payType = payType; } double pay(double hoursWorked, double payRate) { return payType.pay(hoursWorked, payRate); } private enum PayType { WEEKDAY { double overtimePay(double hours, double payRate) { return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT) * payRate / 2; } }, WEEKEND { double overtimePay(double hours, double payRate) { return hours * payRate / 2; } }; private static final int HOURS_PER_SHIFT = 8; abstract double overtimePay(double hrs, double payRate); double pay(double hoursWorked, double payRate) { double basePay = hoursWorked * payRate; return basePay + overtimePay(hoursWorked, payRate); } } }
Effective Item 15 - 枚举