Effective Java ReadingNotes 5

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;
    }
}

可以通过以下两种方法传递完整的扩展枚举类型,并使用它的元素:

  1. <T extends Enum<T> & Operation> Class<T>

  2. Collection<? Extends Operation>

35.注解优先于命名模式

命名模式的缺点:

  1. 无法处理命名失误的情况;

  2. 无法确保它们只用于响应的程序元素上;

  3. 他们没有提供将参数值与程序元素关联起来的好方法。

36.坚持使用Override注解

在每个方法声明中使用Override注解来覆盖超类声明。

37.用标记接口定义类型

标记接口定义的类型是由被标记类的实例实现的;标记注解则没有定义这样的类型;标记接口可以被更加精确地进行锁定。

标记注解胜过标记接口的最大优点在于,它可以通过默认的方式添加一个或者多个注解类型元素,给已被使用的注解类型添加更多的信息,它的另一个优点在于它们使更大的注解机制的一部分。

如果想要定义类型,一定要使用接口。

Gaoge

A student.

Beijing, China ggao.me