第三十八条:检查参数的有效性
第三十九条:必要时进行保护性拷贝
public class Period { private final Date start; private final Date end; public Period(Date start, Date end) { this.start = start; this.end = end; } public Date start() { return start; } public Date end() { return end; } }
写完上面的代码后会认为这是一个很安全的了,不可变,初始化了之后就不可以start和end就不可以改变了,but,真这样?注意Date是可变的
Date start = new Date(); Date end = new Date(); Period period = new Period(start, end); end.setTime(System.currentTimeMillis());
这样就把原本的时间改变了。
public class Period { private final Date start; private final Date end; public Period(Date start, Date end) { this.start = new Date(start.getTime()); this.end = new Date(end.getTime()); } public Date start() { return new Date(start.getTime()); } public Date end() { return new Date(end.getTime()); }
程序改成这样后,这个类就真的不可变了,穿件和向客户端返回数据的时候都不是本身的引用,而是另一个实例。这里也没用,如果用clone方法,客户端还可能编写Date的子类,如果在子类里包含攻击代码,如在子类的实例被创建的时候把指向该实例的引用记录到一个私有的静态列表中。
第四十条:谨慎设计方法签名
这个是对设计API的技巧
- 谨慎的选择方法的名称
- 不要国语追求提供遍历的方法。意思是不要暴露过多的方法,这样会很难学习、使用、文档化。只要提供这个类应该提供的方法就可以了。
- 避免过长的参数列表。
三种方法缩短参数
- 把方法分解多个小方法
- 创建辅助类
- 采用建造者模式(Builder)。前面的有例子,就在类内部建造一个Builder类,在这个里设计各种参数,是根据方法设置的参数已读。外部类的构造器将内部Builder的类的参数赋值给外部类。
对于boolean类型,优先采用enum。
第四十一条:慎用重载
看一下下面的例子
class CollecitonClassifier { public static String classify(Set<?> s) { return "Set"; } public static String classify(List<?> s) { return "List"; } public static String classify(Collection<?> s) { return "Unknown"; } public static void main(String[] args) { Collection<?>[] collections ={new HashSet<String>(), new ArrayList<String>(), new HashMap<String, String>().values()}; for (Collection<?> collection : collections) System.out.println(FourtyFirst.CollecitonClassifier.classify(collection)); } }
期望的结果是"Set" "List" "Unknown",但是上面的结果是三个Unknown。在看下面的例子
class Wine{ String name(){return "wine";} } class Champagne extends Wine{ @Override String name(){return "Champagne";} } public void test() { Wine[] wines = {new Wine(), new Champagne()}; for (Wine wine : wines) System.out.println(wine.name()); }
这样就是所期望的结果"Wine" "Champagne"
为什么第一个的输出和想的不一样,在声明类型的时候声明出来的最终结果就是Collection类型的,因此最终就是进入 public static String classify(Collection<?> s) {}这个方法。而下面方法的覆盖就能根据类型找到具体的方法。
对于重载方法的选择是静态的,而对于覆盖的选择是动态的。对于重载,在编译时就已经确定了类型(Collection)。而对于方法覆盖,是在运行时才知道当前是什么类型,根据类型类确定到底调用哪个类的方法。
还有在用Set和List时要注意,Set有remove(i) 和remove(E)方法,如果你的Set中放的是Integer,注意一下这个地方,很可能想移除的是Set中的元素,而却调用了remove(i)的方法,移除的是索引为i的。List同样要注意。
为了避免重载方法出现的问题,最好不要出现相同参数个数的重载方法,起个其他的名字又不是难事。
第四十二条:慎用可变参数
int sum(int ...arg) {} 可变参数就是这种类型的参数。
第四十三条:返回零长度的数组或者集合,而不是null
第四十四条:为所有导出的API元素编写文档注释