表示泄露是极坏的。如果再碰上一个家长出门的熊孩子看到还开着的电脑,结局可能就是一个被打一个被开,一个配上“他还只是个孩子啊”可能下次还敢,一个丢了生计不知何时才能重新工作。程序员应当是负主要责任的,“熊孩子”千千万,但凡做好防御式拷贝,都可以直接在灯火阑珊处等他们来捣乱了。
防御式拷贝
防御式拷贝,其实就是返回一个全新的对象,可以这样理解:一个孩子很喜欢一个比较脆弱的名贵物品,想要得到它,那么我们用防御式拷贝给他造一个一模一样的(根据唯物主义,真正有价值的还是原本那个),随便他怎么去处置我们造出来的,既给了他的开心,又避免了表示泄露。
一个很典型的例子就是Date和List。它们俩都是可变数据类型,如果不使用防御式拷贝,而仅仅是在原对象上再加一个引用,那么就可能引发严重的错误——如果原先的Date存的是一个纪念日,这时候把它传给一个伙计,伙计发现自己的一个纪念日刚刚好一个月后,于是直接在Date对象上修改它的月份,那么此对象原先创造出来的本意就被改变了,这是我们不希望看到的。
所以,对Date对象使用防御式拷贝是十分必要的,即所有的Date对象x返回的都应该是:
return new Date(x.getTime());
这样就能避免由于一旦被无意中改变造成的非常难于跟踪和发现的错误。
在lab2中也有很多地方用到了防御式拷贝:
//返回包含所有的顶点的集合
/**
* Get all the vertices in this graph.
*
* @return the set of labels of vertices in this graph
*/
@Override public Set<L> vertices() {
return new HashSet<>(vertices);
}
/**
* Get the edges from the vertex.
*
* @return the edges pair (source, weight) from the vertex
*/
public Map<L, Integer> getTargets() {
return new HashMap<>(targets);
}
/**
* Check whether there is an edge from source to vertex.
*
* @param source the target to check
*/
public boolean connectFrom(L source) {
return sources.containsKey(source);
}
关于防御式拷贝,其实也就这么多东西,只是养成这样一个习惯还是比较困难的,总会有那么几个返回直接返回了原来的可变对象。所以,在不变数据类型和可变数据类型与防御式拷贝之间的Compromise需要我们来衡量。