27、优先考虑泛型方法
28、利用有限制通配符来提升API的灵活性
PECS,producer-extends,consumer-super。
//public class Stack<E>{ // public Stack(); // public void push(E e); // public E pop(); // public boolean isEmpty(); //} public void pushAll(Iterator<? extends E> src){ for(E e : src) push(e); } public void popAll(Collection<? super E> dst){ while(!isEmpty()){ dst.add(pop()); } } // Get and Put Principle
所有comparable和comparator都是消费者(Consumer)。
29、优先考虑类型安全的异构容器
30、用enum代替int常量
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH } public enum Orange { NAVEL, TEMPLE, BLOOD }
枚举型在java中非常强大,当需要一组固定常量时,使用enum比int好很多。比如代码可读性,安全性等。
31、enum用实例域代替序数
// bad solution public enum Ensemble { SOLO, DUET, TRIO, QUARTET, QUINTET, SEXTET, SEPTET, OCTET, NONET, DECTET; public int numberOfMusicians() { return ordinal() + 1; } } // // improvement public enum Ensemble { SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), SEXTET(6), SEPTET(7), OCTET(8), NONET(9), DECTET(10), TRIPLE_QUARTET(12); private final int numberOfMusicians; Ensemble(int size) { this.numberOfMusicians = size; } public int numberOfMusicians() { return numberOfMusicians; } }
永远不要像第一种的方式,利用序数访问enum,需要在构造函数中使用参数来初始化。
32、用EnumSet代替位域
public class Text{ public static final int STYLE_BOLD = 1 << 0; // 1 public static final int STYLE_ITALIC = 1 << 1; // 2 public static final int STYLE_UNDERLINE = 1 << 2; // 4 public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8 public void applyStyles(int styles){ // ... } } // text.applyStyles(STYLE_BOLD | STYLE_ITALIC);
以上叫做位图法,但是有更好的方案来传递多组常量——EnumSet。
public class Text{ public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH } // 注意此处,使用的是Set而不是EnumSet public void applyStyles(Set<Style> styles){ // ... } } // text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
33、用EnumMap代替序数索引
任何时候都不要使用enum的ordinal()方法。
34、用接口模拟可伸缩的枚举
35、注解优先于命名模式
36、坚持使用Override注解
38、检查参数的有效性
公有方法检查参数,参数异常需要跑出Exception。私有方法利用断言assertion检查参数。
39、必要时进行保护性拷贝
假设类的客户端会尽其所能来破坏这个类的约束条件,因此你必须保护性的设计程序。以下是一个不可变类的设计。
public Period(Date start, Date end){ this.start = new Date(start); // 使用了值的拷贝,没有使用原对象(指针) this.end = new Date(end); if(this.start.compareTo(this.end)>0) throw new IllegalArgumentException(start + " after " + end) }
注意:保护性拷贝是在检查参数之前进行的,防止多线程的影响。不要使用clone方法进行保护性拷贝。
以上方法防御了传入参数的修改,但是对于get方法获取到的对象,仍然可以被修改,通过以下方法可以防止这种攻击。
public Date start(){ return new Date(start); } public Date end(){ return new Date(end); }
40、谨慎设计方法签名
41、慎用重载
42、慎用可变参数
43、返回0长度的数组或者集合,而不是null
null一般用于表示没有被初始化或处理,如果方法返回了null,则需要在上层做更多的处理,以防止NPE。
44、为所有导出的API元素编写文档注释
正确的javadoc文档,需要每个被导出的类、接口、构造器、方法和域之前增加文档注释。注释应该是对实现透明的,只需要简洁的描述它和客户端之间的约定。并且,还应该附上该方法的副作用。
45、将局部变量的作用域最小化
46、for-each优先于for循环
for-each规避掉了for循环的index变量的引用,通常来说它是不必要的——会增加引入错误的风险,并且风险一旦发生,很难被发现。不过有三种情况下,无法使用for-each(注:在jdk1.8中已经很好的解决了这些问题)。
- 过滤
- 转换
- 平行迭代
48、如果需要精确的答案,请避免使用float和double
float和double是执行的二进制浮点运算,目的是在广泛数值范围上使用精确的快速近似计算而设计的。然而他们并没有提供完全精确的计算(实际应用中,经常会碰到出现x.99999等结果)。尤其是,在进行货币计算时,他们并不适用。比如:
System.out.println(1.03-.42);
得到的结果将是:0.610000000001。
为了解决这个问题,需要使用BigDecimal。然而这也有一些问题,相对于普通的运算,它显得更加麻烦,而且也更慢。通常来说后一个缺点可以忽略,但是前者可能会让人很不舒服。有一种做法是将需要处理的数值*10(或更多),使用int进行计算,不过需要你自己处理四舍五入等操作。
49、基本类型优先于装箱基本类型
- 基本类型只有值,装箱类具有与他们值不同的同一性。
- 基本类型只有功能完备的值,装箱类还具有非功能值:null。所以你可能会碰到NPE
- 基本类型省空间省时间
50、如果有更精确的类型,请避免使用字符串
- 字符串不适合代替其他值的类型。例如:int,boolean等
- 不适合代替枚举类型(第30条)
- 不适合聚集类型
51、当心字符串连接的性能
操作符“+”可以将多个字符串进行连接。但是在大规模使用“+”的情况下,连接n个字符串的开销是n的平房级时间。这是由于字符串的不可变性导致的。在这种情况下请使用StringBuilder进行连接。
52、通过接口引用对象
53、接口优先于反射机制
使用反射机制会带来以下的问题:
- 丧失了编译期类型检查
- 代码笨拙冗长
- 性能损失
反射基本上只适合用在编写组件时、代码分析器、RPC等场景下使用。在使用反射机制时,如果可能,尽可能只通过反射机制实例化对象,而访问方法时,使用已知的接口或者超类。
54、谨慎使用JNI
55、谨慎进行优化
很多计算上的过失都被归咎于效率(没有必要达到的效率),而不是任何其他原因——甚至包括盲目的做傻事。
——William A. Wulf
不要去计较效率上的一些小小的得失,在97%的情况下,不成熟的优化才是一切问题的根源。
——Donald E. Knuth
在优化方面,我们应该遵守两条规则:
规则1:不要进行优化。
规则2(仅针对专家):还是不要进行优化——也就是说,在你还没有绝对清晰的优化方案前,请不要进行优化。
——M. A. Jackson
这些格言比java的出现还要早20年。他们讲述了一个关于优化的深刻事实:优化的弊大于利。
要努力编写好的程序,而不是快的程序。低耦合的重要性远远大于性能。当程序编写得足够低耦合后,通过工具发现了性能瓶颈的代码块,才可以保证对其的修改不影响任何外部环境。
56、遵守普遍的命名规则
57、只针对异常情况才使用异常
不要尝试通过异常机制来做正常代码应该做的事情,比如,检查数组下标。
jvm很少对异常进行优化,因为它只用于不正常的情况。并且,如果你将代码放入try-catch代码块,jvm就丧失了本来可以对它进行的优化。
58、对于可恢复的情况使用受检异常,对于编程错误的情况使用运行时异常
- 如果期望调用者适当的恢复,则需要使用受检异常,强迫调用者食用try-catch代码块,或者将他们抛出去
- 当调用发生前提违例——违反约定的情况时,使用运行时异常,这个时候程序已经无法再执行下去了。例如调用数组的-1索引。