我们上次讨论了关于表示泄露的三种基本形式:访问权限、getter传递引用、constructor接收引用,这次来讨论更加细节的内容
原创性声明:每一个字都是我手敲的。
更加间接的表示泄露-来自浅拷贝
我们对一个引用类型(对象)进行拷贝时,有三种做法。
- 引用拷贝, 通常通过
=
运算符实现。两个引用变量将指向同一个内存地址。 - 浅拷贝,一些方法提供了浅拷贝。我们把一个对象的字段值全部拷贝到另一个对象里,然后把这个对象的引用赋值给新的引用
- 深拷贝,一些方法提供了深拷贝,通常需要手动实现。对于基本数据类型字段,我们把值拷贝到另一个对象;对于引用类型字段,我们
把这个字段深拷贝到另一个对象的这个字段中,最后把新的对象的引用赋值给新的引用。
这三个概念有些抽象,我们必须结合一些例子来进行讲解。但是我们可以先注意一点:引用拷贝是一次赋值,浅拷贝对字段遍历赋值,深拷贝是对字段递归地赋值。
如果防御性拷贝只是浅拷贝,那么依然存在风险,考虑如下代码:
1 | class qaq{ |
和如下的客户端程序
1 | public static void main(String[] args) { |
现在,q中的a字段的第零个元素的label还会是1嘛?答案是否定的。
究其原因,是因为 qaq.getA()返回的是对LinkedList类型的浅拷贝,这个类型中存放的内容是waw,一个可变引用类型。
因此,当我们对浅拷贝(对应qaq.getA()
)调用getter(对应get
)获取一个可变引用,再对这个引用调用mutator(对应setLabel
)
时,就会把拷贝的母本中的对象也修改!
再次体会一下这个逻辑:母本中的内容是一个引用,我们通过浅拷贝获取了这个引用,修改了引用对应的对象,于是母本的内容虽然没有改变,但是母本的内容指向的对象却发生了改变。
这已经很抽象了!为了避免这个方法,对于任何可变类型的List,Map等,在拷贝时要特别注意深浅。如果是List
一些探讨-public final会导致rep exposure吗?
MIT的课件中说表示泄露“meaning that code outside the class can modify the representation directly.”,
不过我认为这是不准确的,即使不能修改ADT的表示,而仅仅能够获取ADT的内部结构,客户端也足以让ADT的抽象性泄露。
我们考虑下面这个数据结构:
1 | /** Represents an immutable right triangle. */ |
其中,HYPOTENUSE被定义为是public static final int,因此它是作为一个不可变类属性。然而,其访问权限是public,这
意味着用户将有可能使用这个特殊的成员。比如说,编写一些依赖于HYPOTENUSE的代码,而一旦这个值改变,那么这些代码都需要进行修改!
要注意,“斜边存储在边数组的哪个位置”完全不是这个ADT应该暴露出来的东西,因此,尽管它是不可被修改的,但是它依然会导致表示泄露。