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、实现了 ComparableSerializable接口。

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 Java 5.避免创建重复的对象

包邮送书 | Effective C++/Effective Java/Effective Python 任选

Java:Effective java学习笔记之 避免使用终结方法

Java:Effective java学习笔记之 静态工厂方法的简单理解和使用

[读书笔记]Effective Java 第一章