Effective Java 5
Posted wutingjiawill
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Effective Java 5相关的知识,希望对你有一定的参考价值。
---恢复内容开始---
Item 34 使用枚举代替 int常量
1、使用枚举类型代替int枚举模式。
2、Int枚举模式是常变量,很难转换成好的打印字符,没有可靠的方法迭代其中的常量以及获取size.
3、枚举类型:
1 public enum Apple { FUJI, PIPPIN, GRANNY_SMITH } 2 public enum Orange { NAVEL, TEMPLE, BLOOD }
4、枚举类型通过一个public static final 域输出其中每一个枚举常量。
5、枚举类型是final,没有可访问的构造器,也就是说是单例。
6、枚举类型提供了编译器的类型安全。
7、可以删除,添加或者重排序枚举元素,而不需要重新编译。
8、有着很好的toString方法。
9、枚举类型可以添加任意方法和域可以实现任意接口。
10、实现了 Comparable和 Serializable接口。
11、例子:
1 // Enum type with data and behavior 2 public enum Planet { 3 MERCURY(3.302e+23, 2.439e6), 4 VENUS (4.869e+24, 6.052e6), 5 EARTH (5.975e+24, 6.378e6), 6 MARS (6.419e+23, 3.393e6), 7 JUPITER(1.899e+27, 7.149e7), 8 SATURN (5.685e+26, 6.027e7), 9 URANUS (8.683e+25, 2.556e7), 10 NEPTUNE(1.024e+26, 2.477e7); 11 private final double mass; // In kilograms 12 private final double radius; // In meters 13 private final double surfaceGravity; // In m / s^2 14 // Universal gravitational constant in m^3 / kg s^2 15 private static final double G = 6.67300E-11; 16 // Constructor 17 Planet(double mass, double radius) { 18 this.mass = mass; 19 this.radius = radius; 20 surfaceGravity = G * mass / (radius * radius); 21 } 22 public double mass() { return mass; } 23 public double radius() { return radius; } 24 public double surfaceGravity() { return surfaceGravity; } 25 public double surfaceWeight(double mass) { 26 return mass * surfaceGravity; // F = ma 27 } 28 }
12、所有域自然是final的,不过最好使它private并提供public访问器。
13、调用例子:
1 public class WeightTable { 2 public static void main(String[] args) { 3 double earthWeight = Double.parseDouble(args[0]); 4 double mass = earthWeight / Planet.EARTH.surfaceGravity(); 5 for (Planet p : Planet.values()) 6 System.out.printf("Weight on %s is %f%n",p, p.surfaceWeight(mass)); 7 } 8 }
14、.value()方法,按声明的顺序返回包含所有枚举元素的数组。
15、结果:
1 Weight on MERCURY is 69.912739 2 Weight on VENUS is 167.434436 3 Weight on EARTH is 185.000000 4 Weight on MARS is 70.226739 5 Weight on JUPITER is 467.990696 6 Weight on SATURN is 197.120111 7 Weight on URANUS is 167.398264 8 Weight on NEPTUNE is 210.208751
16、删除一个元素不会影响剩余的元素,但是如果再进行重新编译,会提示一个有用的错误。
17、尽可能降低枚举方法的访问权限。
18、如果对每个枚举元素想要有不同的方法:
1 // Enum type that switches on its own value - questionable 2 public enum Operation { 3 PLUS, MINUS, TIMES, DIVIDE; 4 // Do the arithmetic operation represented by this constant 5 public double apply(double x, double y) { 6 switch(this) { 7 case PLUS: return x + y; 8 case MINUS: return x - y; 9 case TIMES: return x * y; 10 case DIVIDE: return x / y; 11 } 12 throw new AssertionError("Unknown op: " + this); 13 } 14 }
19、一个更好的方法是 在枚举类型中 声明一个抽象类。然后在每一个枚举元素的常量具体类体(constant-specific class body) 中重写它。这些方法叫做常量具体方法实现(constant-specific method implementation):
1 // Enum type with constant-specific method implementations 2 public enum Operation { 3 PLUS {public double apply(double x, double y){return x + y;}}, 4 MINUS {public double apply(double x, double y){return x - y;}}, 5 TIMES {public double apply(double x, double y){return x * y;}}, 6 DIVIDE{public double apply(double x, double y){return x / y;}}; 7 public abstract double apply(double x, double y); 8 }
20、与之前数据结合的例子:
1 // Enum type with constant-specific class bodies and data 2 public enum Operation { 3 PLUS("+") {public double apply(double x, double y) { return x + y; }}, 4 MINUS("-") {public double apply(double x, double y) { return x - y;}}, 5 TIMES("*") {public double apply(double x, double y) { return x * y;}}, 6 DIVIDE("/") {public double apply(double x, double y) { return x / y;}}; 7 private final String symbol; 8 Operation(String symbol) { this.symbol = symbol; } 9 @Override public String toString() { return symbol; } 10 public abstract double apply(double x, double y); 11 }
1 public static void main(String[] args) { 2 double x = Double.parseDouble(args[0]); 3 double y = Double.parseDouble(args[1]); 4 for (Operation op : Operation.values()) 5 System.out.printf("%f %s %f = %f%n",x, op, y, op.apply(x, y)); 6 }
21、枚举类型有个 valueof(String)方法,将常量的名字转换成常量本身。
22、如果复写了toString方法,考虑写一个fromString方法把String形式的表示转化为相应的枚举元素。例子:
// Implementing a fromString method on an enum type private static final Map<String, Operation> stringToEnum =Stream.of(values()).collect(toMap(Object::toString, e -> e)); // Returns Operation for string, if any public static Optional<Operation> fromString(String symbol) { return Optional.ofNullable(stringToEnum.get(symbol)); }
23、在Java8之前 使用对于hash map的迭代代替value()将值插入。但是想要通过每个元素的构造器进行插入是不行的。
24、枚举构造器不允许访问静态域,除了常变量。因为在构造器执行的时候静态域还未被初始化。同样也不能通过构造器访问另一个枚举常量。
25、常量具体方法实现(constant-specific method implementation)的一个缺点是使枚举常量共用代码困难,并且在添加新的枚举元素时容易出错。例子:
// Enum that switches on its value to share code - questionable enum PayrollDay { MONDAY, TUESDAY, WEDNESDAY, THURSDAY,FRIDAY,SATURDAY, SUNDAY; private static final int MINS_PER_SHIFT = 8 * 60; int pay(int minutesWorked, int payRate) { int basePay = minutesWorked * payRate; int overtimePay; switch(this) { case SATURDAY:
case SUNDAY: // Weekend overtimePay = basePay / 2; break; default: // Weekday overtimePay = minutesWorked <= MINS_PER_SHIFT ?0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2; } return basePay + overtimePay; } }
26、好的方法,使用内部枚举类型:
// The strategy enum pattern enum PayrollDay { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND); private final PayType payType; PayrollDay(PayType payType) { this.payType = payType; } PayrollDay() { this(PayType.WEEKDAY); } // Default int pay(int minutesWorked, int payRate) { return payType.pay(minutesWorked, payRate); } // The strategy enum type private enum PayType { WEEKDAY {
int overtimePay(int minsWorked, int payRate) { return minsWorked <= MINS_PER_SHIFT ? 0 :(minsWorked - MINS_PER_SHIFT) * payRate / 2; } }, WEEKEND { int overtimePay(int minsWorked, int payRate) { return minsWorked * payRate / 2; } }; abstract int overtimePay(int mins, int payRate); private static final int MINS_PER_SHIFT = 8 * 60; int pay(int minsWorked, int payRate) { int basePay = minsWorked * payRate; return basePay + overtimePay(minsWorked, payRate); } } }
27、Switch方法用在当你不能控制枚举类时增加枚举类型的常量具体行为(constant-specific behavior)。
1 // Switch on an enum to simulate a missing method 2 public static Operation inverse(Operation op) { 3 switch(op) { 4 case PLUS: return Operation.MINUS; 5 case MINUS: return Operation.PLUS; 6 case TIMES: return Operation.DIVIDE; 7 case DIVIDE: return Operation.TIMES; 8 default: throw new AssertionError("Unknown op: " + op); 9 } 10 }
28、在需要一系列常值并且在编译期就知道它的成员时,以及自然应该是枚举的时候(如一周7天等)使用枚举类型。
29、如第7条所说,枚举类型中的元素并不必须保持固定。
Item 35 使用实例域代替ordinals
1、所有枚举类型有一个ordinal方法,用于返回枚举元素在枚举类型中的位置(int)。这对于进行添加或删除元素来说时非常糟糕的。
1 // Abuse of ordinal to derive an associated value - DON‘T DO THIS 2 public enum Ensemble { 3 SOLO, DUET, TRIO, QUARTET, QUINTET,SEXTET, SEPTET, OCTET, NONET, DECTET; 4 public int numberOfMusicians() { return ordinal() + 1; } 5 }
2、永远不要通过ordinal 获得一个和枚举元素有关的值。应该作为一个实例域进行储存:
1 public enum Ensemble { 2 SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),NONET(9), DECTET(10), TRIPLE_QUARTET(12); 3 private final int numberOfMusicians; 4 Ensemble(int size) { this.numberOfMusicians = size; } 5 public int numberOfMusicians() { return numberOfMusicians; } 6 }
3、ordinal()方法只被设计用于基于枚举类型的数据结构 例如EnumSet EnumMap。
Item 36 使用EnumSet代替 位(bit)域
1、使用int 枚举模式在bit域上有这比普通的域上更多的缺点,比如需要确定最大长度,无法迭代等:
1 // Bit field enumeration constants - OBSOLETE! 2 public class Text { 3 public static final int STYLE_BOLD = 1 << 0; // 1 4 public static final int STYLE_ITALIC = 1 << 1; // 2 5 public static final int STYLE_UNDERLINE = 1 << 2; // 4 6 public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8 7 // Parameter is bitwise OR of zero or more STYLE_ constants 8 public void applyStyles(int styles) { ... } 9 }
2、一个好的例子使用EnumSet,它实现了Set接口因此可以使用所有Set的操作,并且类型安全。在内部每个EnumSet代表一个位向量:
1 public class Text { 2 public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH } 3 // Any Set could be passed in, but EnumSet is clearly best 4 public void applyStyles(Set<Style> styles) { ... } 5 }
1 text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
3、唯一的缺点是无法创建一个不变的EnumSet,但是可以使用Collections.unmodifiableSet进行包装 虽然会有性能损失。
Item 37 使用EnumMap 代替 序数索引(ordinal indexing))
例子:
1 class Plant { 2 enum LifeCycle { ANNUAL, PERENNIAL, BIENNIAL } 3 final String name; 4 final LifeCycle lifeCycle; 5 Plant(String name, LifeCycle lifeCycle) { 6 this.name = name; 7 this.lifeCycle = lifeCycle; 8 } 9 @Override public String toString() { 10 return name; 11 } 12 }
1、一个错误的使用例子:
1 // Using ordinal() to index into an array - DON‘T DO THIS! 2 Set<Plant>[] plantsByLifeCycle =(Set<Plant>[]) new Set[Plant.LifeCycle.values().length]; 3 for (int i = 0; i < plantsByLifeCycle.length; i++) 4 plantsByLifeCycle[i] = new HashSet<>(); 5 for (Plant p : garden) 6 plantsByLifeCycle[p.lifeCycle.ordinal()].add(p); 7 // Print the results 8 for (int i = 0; i < plantsByLifeCycle.length; i++) { 9 System.out.printf("%s: %s%n",Plant.LifeCycle.values()[i], plantsByLifeCycle[i]); 10 }
首先的问题是使用了泛型数组使得无法获得类型安全的保证。更重要的问题是当使用由枚举叙述所索引的数组时,int的正确性必须自行保证。
2、正确的方式使用EnumMap:
1 // Using an EnumMap to associate data with an enum 2 Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle =new EnumMap<>(Plant.LifeCycle.class); 3 for (Plant.LifeCycle lc : Plant.LifeCycle.values()) 4 plantsByLifeCycle.put(lc, new HashSet<>()); 5 for (Plant p : garden) 6 plantsByLifeCycle.get(p.lifeCycle).add(p); 7 System.out.println(plantsByLifeCycle);
注意构造器使用了Class对象作为key
可以使用流进行进一步的简化:
1 // Naive stream-based approach - unlikely to produce an EnumMap! 2 System.out.println(Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle)));
1 // Using a stream and an EnumMap to associate data with an enum 2 System.out.println(Arrays.stream(garden).collect(groupingBy(p -> p.lifeCycle,() -> new EnumMap<>(LifeCycle.class), toSet())));
3、一个错误的例子 使用两个枚举的ordinal 来代替一个二维数组的索引:
1 // Using ordinal() to index array of arrays - DON‘T DO THIS! 2 public enum Phase { 3 SOLID, LIQUID, GAS; 4 public enum Transition { 5 MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT; 6 / / Rows indexed by from-ordinal, cols by to-ordinal 7 private static final Transition[][] TRANSITIONS = {{ null, MELT, SUBLIME },{ FREEZE, null, BOIL },{ DEPOSIT, CONDENSE, null }}; 9 // Returns the phase transition from one phase to another 10 public static Transition from(Phase from, Phase to) { 11 return TRANSITIONS[from.ordinal()][to.ordinal()]; 12 } 13 } 14 }
这显然也是一个极其脆弱的结构。
4:正确的例子:
// Using a nested EnumMap to associate data with enum pairs public enum Phase { SOLID, LIQUID, GAS; public enum Transition { MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID); private final Phase from; private final Phase to; Transition(Phase from, Phase to) { this.from = from; this.to = to; } // Initialize the phase transition map private static final Map<Phase, Map<Phase, Transition>>m = Stream.of(values()).collect(groupingBy(t -> t.from,() -> new EnumMap<>(Phase.class),toMap(t -> t.to, t -> t,(x, y) -> y, () -> new EnumMap<>(Phase.class)))); public static Transition from(Phase from, Phase to) { return m.get(from).get(to); } } }
1 // Adding a new phase using the nested EnumMap implementation 2 public enum Phase { 3 SOLID, LIQUID, GAS, PLASMA; 4 public enum Transition { 5 MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID),IONIZE(GAS, PLASMA), DEIONIZE(PLASMA, GAS); 6 ... // Remainder unchanged 7 } 8 }
以上是关于Effective Java 5的主要内容,如果未能解决你的问题,请参考以下文章
5. Effective Java 第三版——使用依赖注入取代硬连接资源
包邮送书 | Effective C++/Effective Java/Effective Python 任选
Java:Effective java学习笔记之 避免使用终结方法