Effective Java:善于使用枚举(enum)和注解(annotation)

来源:互联网 发布:彩票 java编程 面试题 编辑:程序博客网 时间:2024/06/08 06:15

30 用enum代替int常量

编写程序的时候更加安全和方便。

vlaueOf(String): 它将常量名字转变成枚举本身。

ordinal(): 返回枚举在数字中的位置。

一个enum用的十分好的例子:

太阳系8个行星中,每颗行星都有其质量和半径,通过这两个属性可以计算出它的表面重力。以下是行星的枚举:

public enum Planet{  MERCURY(3.32e+23,  2.439e6),  VENUS  (4.869e+24, 6.052e6),  ...  EARTEH (5.975e+24, 6.378e6);  private static final double G = 6.678E-11;  private final doiuble mass;  private final doiuble radius;  rpivate final doiiuble surfaceGravite;  Planet(double mass, doiuble radius){    this.mass = mass;    this.radius = radius;  }  public double surfaceWeight(double mass){    //计算方法  }  //getter方法}

下面这个程序,可以漂亮的打印出,一个物体在各大行星的重量:

public class WeightTable{  public static void main(String[] args){    double earthWeight = DOuble.parseDouble(arg[0]);    double mass = 111;    for(Planet p : Planet.values())        System.out.printf("weight on %s is %f%n", p,p.surfaceweight(mass));  }}

特定于常量的方法实现

对于同样的方法,不同的常量有不同的实现。

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;}};  abstract double apply(double x, double y);}

不太好的实现,在以后新增常量的时候,可能会忘记添加方法

double apply(double x, double y){  switch(this){    case PLUS: return x+y;    ...  }}

特定于常量的方法实现有一个不好的地方,就是容器产生许多的样板代码。如何解决这个问题,可以使用策略枚举。

如果多个枚举同时共享相同的行为,考虑策略枚举

以支付加班报酬为例,不同的时间的加班计价方式不同,那么在新建枚举类型的时候,必须选择不同的计价方式:

enum PayrollDay{  MONDAY(PayType.WEEKDAY),  TUESDAY(PayType.WEEKDAY)  ....;  private final PayType payType;  PayrollDay(PayType paytype){this.payType = paytype;}  double pay(double hourseWorked, double payRate){    return payType.pay(hoursWorked,payRate);  }  private enum PayType{    WEEKDAY{double overtimePay(double, double){...}        },    //其它枚举类型    abstract double overtimePay(double, double);    double pay(double, double){      //里面用到了overtimePay    }  }}

31 用实例域代替序数

每个枚举和一个单独的int值相关联。不要依赖枚举本身的这种依赖的int值,而是应该自己定义一个int值,在构造的时候赋给它。不然,很难维护。

32 用EnumSet代替位域

33 用EnumMap代替序数索引

一个使用的例子:

//Herb是一个类class Herb{  public enum Type {....}  private final Type type;}Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>(Herb.Type.class);//如果使用序数的话,这里是一个数for(Herb.Type t: Herb.Type.values())    herbsByType.put(t, new HashSet<Herb>());//数组的话,加入的规则是按照Type的类型for(Herb h :garden)    herbsByType.get(h.type).add(h);

最好不要使用序数来索引数组,而要使用EnumMap

34 用接口模拟可伸缩的枚举

在30中提到的,特定于常量的方法的实现,可以用接口声明这个抽象方法,枚举拓展这个接口,在特定方法中实现之。至于伸缩性,现在不甚理解。

35 注解优先于命名模式

一般使用命名模式,表示有些程序元素要通过某种工具或者框架进行特殊处理。注意,这个命名模式不是设计模式中的命令模式,而是对元素命名时的一种规则,比如JUnit测试要求用户要用test作为测试方法开头。

命名模式的缺点:

  • 文字拼写容易出错。
  • 无法确保他们只用于相应的程序元素上。
  • 没有提供将参数值和程序元素关联起来的好方法。意即:命名方式不能带参数,对元素处理的时候不方便。即使成功实现,命名或代码也不好看。

举个使用例子:

以测试为例:

旧版JUnit:

void testTargetMethod(){  ...}

新版:

@Testvoid targetMethod(){  ...}

这样子就不用怕拼写出错啦,也可以携带参数。

36 坚持使用Override注解

覆盖方法的时候,使用Override注解,编译器能帮确定是否覆盖了这个方法,而不是编程了重载该方法:

例子:

public class TestClass{  private final char first;  ...  public boolean equals(TestClass a){    return this.first = a.first;  }}

如果没有注解的话,那么,就重载了equals 方法了,因为equals的定义是这个样子的:

public boolean equals(Object o);

运行时,完全没有问题。

更多的帮助:

在Java1.6以上版本,在抽象类或者接口中,标注所有想要覆盖的方法,来覆盖超类或者超接口的方法,无论这些方法是抽象的还是具体的。如,Set 接口没有给Collection接口添加方法,因此他应该在它的所有方法总柏阔Override注解,确保绝对没有给Collection接口添加新的方法。

总结:覆盖方法都打上该标准,具体类实现接口方法除外,因为编译器会出现提醒。

37 用标记接口定义类型

标记接口是没有包含方法声明的接口,只是表明一个类实现了具有某种属性的接口,为此给这个类打上个标记。

这种打标记的方法有两种,一种是标记接口、另一种是标记注解 (Spring 中@Component

标记接口优势:

  • 标记接口定义的类型是被标记类的实例实现的;标记注解则没有定义这样的类型。(???)
  • 可以却被更精确的锁定。如果注解类型利用@Target 声明,它可以被应用到任何类或者接口。假设它定义成一个标记接口,就可以用它将唯一的接口拓展成它适用的接口。

标记注解优势:

  • 默认的方式添加一个或者多个注解类型元素,给已被使用的注解类型添加更多新型。随着时间推移,简单的标准类型可以演变成更加丰富的注解类型。这种演变对标记接口是不可能的,应为它通常不可能在实现接口之后在给它添加方法。
  • 标记注解是注解机制的一部分。因此,标记注解在哪些支持注解的编程元素的框架中同样具有一致性。

如果标记应用到任何程序元素(方法/域),使用注解,接口无法标记方法等。如果编写的方法要接受一个或多个这种标记参数的方法,优先使用标记接口。这样,接口作为相关方法的参数类型,它真正可以为你提供编译时进行类型检查的好处。

原创粉丝点击