在 Subsets 的实现中,我们用到了如下的代码记录每一个找到的集合
results.add(new ArrayList<Integer>(subset));
事实上,这句话是调用了 ArrayList 的一个构造函数(Constructor),这个构造函数可以接受另外一个 ArrayList 作为其初始化的状态。
这种方式,我们叫它深度拷贝(Deep Copy),又叫做硬拷贝(Hard Copy)或者克隆(Clone,名字多得老纸记不住啊)。与之对应的就有 软拷贝(Soft copy),又名引用拷贝(Reference Copy)。
不使用 Deep copy 会怎样呢?
我们来看看不使用 Deep copy 会怎样:
List<Integer> subset = new ArrayList<>();
subset.add(1); // 此时 subset 是 [1]
List<List<Integer>> results = new ArrayList<>();
results.add(subset); // 此时 results 是 [[1]]
subset.add(2); // 此时 subset 是 [1,2]
results.add(subset); // 此时你以为 results 是 [[1], [1,2]] 而事实上他是[[1,2], [1,2]]
subset.add(3); // 此时 results 里是 [[1,2,3], [1,2,3]]
我们看到由于每一次 results.add 都是加入了相同的变量 subset,因此如果 subset 有变化,那么 result 里的记录就会同步的发生变化。原因是 results.add(subset) 加入的是 subset 的 reference,也就是 subset 在内存中的地址。那么事实上,当 results 里有两个 subset 的时候,相当于存储的是两个内存地址,而这两个内存地址又是一样的,才会导致如果这个内存地址里存的东西发生了变化,results 看起来就每个元素都发生了变化。
参数中引用传递
来看这段代码
public void func(List<Integer> subset) {
subset.add\(1\);
}
public void main() {
List<Integer> subset = new ArrayList<>\(\);
// 此时 subset 是 \[\]
func\(subset\);
// 此时 subset 就是 \[1\] 了
}
可能你会奇怪,不是说修改参数不会影响到函数之外的参数么?也就是:
public void func(int x) {
x = x + 1;
}
public void main() {
int x = 0;
func\(x\);
// 此时 x 仍然是 0
}
上面两者的区别在于,人们习惯性的认为 subset.add 和 x = x + 1 都是对参数进行了修改。而事实上,x = x + 1 确实是对参数进行了修改,这个修改只在函数func的局部有效,出了func回到main就失效了。而 subset.add 并没有修改 subset 这个参数本身,而只是在 subset 所指向的内存空间中增加了一个新的元素,这个操作是永久性的,不是临时的,是全局有效的,不是局部有效的。那么怎么样才是对 subset 这个参数进行了修改呢?比如:
public void func(List<Integer> subset) {
subset = new ArrayList<Integer>\(\);
subset.add\(1\);
}
public void main() {
List<Integer> subset = new ArrayList<>\(\);
// 此时 subset 是 \[\]
func\(subset\);
// 此时 subset 还是 \[\]
}
我们可以看到如果你的修改操作是 参数x = ... 那么这才是对参数x的修改,而 参数x.call_method() 并不是对参数 x 本身的修改。