No30 用enum代替int常量
一:综述
int枚举模式,示范:
// The int enum pattern - severely deficient! public static final int APPLE_FUJI = 0; public static final int APPLE_PIPPIN = 1; public static final int APPLE_GRANNY_SMITH = 2;
还有一种是这种模式的变体,使用String常量代替int常量,称为String枚举模式。缺点:
- int枚举是编译时常量,如果它发生了变化,客户端需要重新编译;
- 将int枚举常量翻译成可打印的字符串,并没有很便利的方法,你所见到的就是一个数字(比如调试,跟踪值等);
- String枚举中的硬编码常量如果包含有书写错误,那么这样的错误在编译时不会被检测到,运行时会出错。
JDK1.5发行版本开始,提出了另一种可以替代的解决方案,可以避免int和String枚举模式的缺点,并提供许多额外的好处。示范:
public enum Apple {FUJI, PIPPIN, GARNNY_SMITH}
- 枚举提供了编译时的类型安全。如果声明一个参数的类型为Apple,就可以保证,被传到该参数上的任何非null的对象引用一定属于三个有效的Apple值之一。
- 常量值没有被编译到客户端代码中;
- 允许添加任意的方法和域,并实现任意的接口。
二:有关枚举的一个好例子:
// Enum type with data and behavior - Pages 149-150 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 } }
// Takes earth-weight and prints table of weights on all planets - Page 150 public class WeightTable { public static void main(String[] args) { if(args.length == 0) { args = new String[]{"100"}; } 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 p = Planet.MERCURY; System.out.printf("dd Weight on %s is %f%n", p, p.surfaceWeight(mass)); } }
三:复杂一些的例子,比较如下两段代码:
代码一:
// Enum type that switches on its own value – questionable public enum Operation { PLUS, MINUS, TIMES, DIVIDE; // Do the arithmetic op represented by this constant 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; } throw new AssertionError("Unknown op:" + this); } }
上述代码可行,但是不太好看。如果没有throw语句,它就不能进行编译。另外,如果添加了新的枚举常量,却忘记给switch添加相应的条件,枚举仍然可以编译,但当你试图运用新的运算时,就会运行失败。
代码二:
// Enum type with constant-specific class bodies and data - Page 153 import java.util.*; 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); // Implementing a fromString method on an enum type - Page 154 private static final Map<String, Operation> stringToEnum = new HashMap<String, Operation>(); static { // Initialize map from constant name to enum constant for (Operation op : values()) stringToEnum.put(op.toString(), op); } // Returns Operation for string, or null if string is invalid public static Operation fromString(String symbol) { return stringToEnum.get(symbol); } // Test program to perform all operations on given operands 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)); } }
输出:
2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000
注:枚举类型中的抽象方法必须被它所有常量中的具体方法覆盖。
No32 用EnumSet代替位域
如果一个枚举类型的元素主要用在集合中,一般就使用int枚举模式,将2的不同倍数赋予每个常量:
// Bit field enumeration constants – OBSOLETE! import java.util.*; public class Text { public static final int STYLE_BOLD = 1 << 0; //1 public static final int STYLE_ITATIC = 1 << 1; //2 public static final int STYLE_UNDERLINE = 1 << 2; //4 public static final int STYLE_STRIKETHROUGH = 1 << 3; //8 //Parameter is bitwise OR of zero or more STYLE_ constants public void applyStyles(int styles){ // 根据styles值进行拆分,判断是哪些参数... }; // Sample use public static void main(String[] args) { Text text = new Text(); text.applyStyles(STYLE_BOLD|STYLE_ITATIC); } }
下面是一个范例改成枚举代替位域后的代码,它更加简洁、更加清楚、也更加安全。
// EnumSet - a modern replacement for bit fields - Page 160 import java.util.*; 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 } // Sample use public static void main(String[] args) { Text text = new Text(); text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC)); } }
No36 坚持使用Override注解
如果你能判断出下面的代码输出结果是260,而不是26,那么你的水平应该足够高。即使这样,也不应该忽略Override注解。
// Can you spot the bug? - Page 176 import java.util.*; public class Bigram { private final char first; private final char second; public Bigram(char first, char second) { this.first = first; this.second = second; } public boolean equals(Bigram b) { return b.first == first && b.second == second; } public int hashCode() { return 31 * first + second; } public static void main(String[] args) { Set<Bigram> s = new HashSet<Bigram>(); for (int i = 0; i < 10; i++) for (char ch = 'a'; ch <= 'z'; ch++) s.add(new Bigram(ch, ch)); System.out.println(s.size()); } }
在上面的代码中的方法equals之前增加@Override注解,则编译出错!因为equals需要的是Object参数,而不是Bigram参数。这样,你应该能够明白@Override的作用了吧。