Effective Java阅读笔记(5)
Effective Java第6章 枚举和注解
30.用enum代替int常量
public 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 hours, double payRate);
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
return basePay + overtimePay(hoursWorked, payRate);
}
}
}
与int常量相比,枚举要易读得多,也更加安全,功能更加强大。许多枚举都不需要显式的构造器或者成员,但许多其它枚举则受益于“每个常量与属性的关联”以及“提供行为受这个属性影响的方法”。只有极少数的枚举受益于将多种行为与单个方法关联。在这种相对少见的情况下,特定于常量的方法要优先于启用自有值的枚举。如果多个枚举常量同时享有相同的行为,则考虑策略枚举。
31.用实例域代替序数
永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实力域中。
public enum Ensemble{
SOLO(1), DUET(2), TRIO(3), QUINTET(4), SEXTET(5),
SEPTET(7), OCTET(8), DOUBLE_QUARTET(8), NONET(9),
DECTET(10), TRIPLE_QUARTRT(12);
private final int numberOfMusicians;
Ensemble(int size){
this.numberOfMusicians = size;
}
public int numberofMusicians(){
return numberOfMusicians;
}
}
Enum规范中谈到ordinal时这么写到:“大多数的程序员都不需要这个方法。它是设计成用于像EnumSet和EnumMap这种基于枚举的通用数据结构的。”除非你在编写的是这种数据结构,否则最好完全避免使用ordinal方法。
32.用EnumSet代替位域
EnumSet类集位域的简洁和性能优势及第30条中所述的枚举类型的所有的优点于一身。
public class Text{
public enum TextStyle{BOLD, ITALIC, UNDERLINE, STRIKETHROUGH}
public void applyStyles(Set<TextStyle> styles){...}
}
下面是将EnumSet实例传递给applyStyles方法的客户端代码。EnumSet提供了丰富的静态工厂来创建集合,其中一个如这个代码所示:text.applyStyles(EnumSet.of(TextStyle.BOLD,TextStyle.ITALIC));
33.用EnumMap代替序数索引
序数索引是指依赖于枚举成员在枚举中的序数来进行数组索引,如:
public class Plant{
public enum Type { Fruit, Vegetables, Tree}
private final String name;
private final Type type;
Plant(String name, Type type){
this.name = name;
this.type = type;
}
}
Set<Plant>[] plants = (Set[]) new Set[Plant.Type.valuse().lenght];
//根据植物的类型,分别把所有的植物放入三个set中
for(int i = 0; i < plant.lenght; i++){
plant[i] = new HashSet();
}
for(Plant p : garden){ //garden里放了所有的植物
plant[p.type.ordinal()].add(p) //反面教材:利用了枚举的序数来得到想要的数组索引,用户在其他地方可以不使用ordinal函数,而直接使用int值来访问,就可能出错
}
应该使用EnumMap来实现,EnumMap内部是采用数组实现的,具有Map的丰富功能和类型安全以及数组的效率:
Map<Plant.Type, Set> plants = new EnumMap<Plant.Type, Set>(Plant.Type.class); //构造函数需要 键 类型的Class对象
//根据植物的类型,分别把所有的植物放入三个set中
for(Plant.Type type : Plant.Type.valuse()){
plant.put(type, new HashSet);
}
for(Plant p : garden){ //garden里放了所有的植物
plant.get(p.type).add(p) //用户必须使用正确的键值来访问,即Type类型
}
当需要多维关系时,可以使用EnumMap<..., EnumMap<...>>。
34.用接口模拟可伸缩的枚举
虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟。这样允许客户端编写自己的枚举来实现接口。如果API是根据接口编写的,那么在可以使用基础枚举类型的任何地方,也都可以使用这些枚举。
public interface Operation{
double apply(double x,double y);
}
public enum BasicOperation implements Operation{
PLUS("^"){
public double apply(double x,double y){ return Math.pow(x,y);}
},
MINUS("%"){
public double apply(double x,double y){ return x % y;}
};
private final String symbool;
BasicOperation(String symbool){ this.symbool = symbool}
@Override
public String toString() {
return symbool;
}
@Override
public double apply(double x, double y) {
return 0;
}
}
可以通过以下两种方法传递完整的扩展枚举类型,并使用它的元素:
<T extends Enum<T> & Operation> Class<T>Collection<? Extends Operation>
35.注解优先于命名模式
命名模式的缺点:
无法处理命名失误的情况;
无法确保它们只用于响应的程序元素上;
他们没有提供将参数值与程序元素关联起来的好方法。
36.坚持使用Override注解
在每个方法声明中使用Override注解来覆盖超类声明。
37.用标记接口定义类型
标记接口定义的类型是由被标记类的实例实现的;标记注解则没有定义这样的类型;标记接口可以被更加精确地进行锁定。
标记注解胜过标记接口的最大优点在于,它可以通过默认的方式添加一个或者多个注解类型元素,给已被使用的注解类型添加更多的信息,它的另一个优点在于它们使更大的注解机制的一部分。
如果想要定义类型,一定要使用接口。