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(){
...
}
新版:
@Test
void 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
声明,它可以被应用到任何类或者接口。假设它定义成一个标记接口,就可以用它将唯一的接口拓展成它适用的接口。
标记注解优势:
- 默认的方式添加一个或者多个注解类型元素,给已被使用的注解类型添加更多新型。随着时间推移,简单的标准类型可以演变成更加丰富的注解类型。这种演变对标记接口是不可能的,应为它通常不可能在实现接口之后在给它添加方法。
- 标记注解是注解机制的一部分。因此,标记注解在哪些支持注解的编程元素的框架中同样具有一致性。
如果标记应用到任何程序元素(方法/域),使用注解,接口无法标记方法等。如果编写的方法要接受一个或多个这种标记参数的方法,优先使用标记接口。这样,接口作为相关方法的参数类型,它真正可以为你提供编译时进行类型检查的好处。