浅拷贝与深拷贝

0x00 浅拷贝和深拷贝

浅拷贝会创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制。如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。看如下代码:

public class Main {
static class Test {
private ArrayList<String> strings = new ArrayList<>();
public void addString(String s) {
strings.add(s);
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
for (String s : strings) {
stringBuilder.append(s).append(" ");
}
return stringBuilder.toString();
}
}
public static void main(String[] args) {
Test t1 = new Test();
t1.addString("Hello");
System.out.println(t1); // 输出t1
Test t2 = t1; // 浅拷贝
t2.addString("World!");
System.out.println(t1); // 输出t1
}
}

此代码的输出值为:

Hello
Hello World!

因为代码Test t2 = t1;为浅拷贝,对象t2中的strings为引用类型变量,在拷贝的过程中,仅仅是把t1中的那个strings的地址赋值给t2中的引用类型变量strings,其仍等于对象t1中的那个strings所指向的内存地址值。所以当我们使用t2.addString("World!");时,仍然相当于给t1中的strings添加元素。所以第二行的输出为Hello World!

深拷贝则恰恰相反,深拷贝会创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容

0x01 深拷贝的实现

深拷贝的实现需要类实现Cloneable接口,而且需要重写其clone方法,以手动实现深拷贝。当拷贝的时候,需要调用clone方法进行深拷贝,如果仍然使用=赋值,那么结果仍为浅拷贝。

public class Main {
static class Test implements Cloneable {
private ArrayList<String> strings = new ArrayList<>();
public void addString(String s) {
strings.add(s);
}
@Override
protected Object clone() throws CloneNotSupportedException {
Test test = (Test) super.clone();
// 重新创建一个新的ArrayList
// 然后手工复制其中的值到新的对象中以实现深拷贝
test.strings = new ArrayList<>();
for (String s : strings) {
test.addString(s);
}
return test;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
for (String s : strings) {
stringBuilder.append(s).append(" ");
}
return stringBuilder.toString();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Test t1 = new Test();
t1.addString("Hello");
System.out.println(t1);
Test t2 = (Test) t1.clone(); // 深拷贝
t2.addString("World!");
System.out.println(t1);
}
}

此代码输出即为:

Hello
Hello

因为t2t1中的strings分别为指向两块不同内存区域的引用类型变量。当然我们也可以通过对象的序列化来实现深拷贝。