Java 之深拷贝与浅拷贝

前言

对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部数据。

浅拷贝

浅拷贝(Shallow Copy)只拷贝指向某个对象的指针(内存地址),而不拷贝对象本身,新旧对象还是共享同一块内存,类似一个分支。

  • 拷贝基本类型
    • 拷贝的就是基本类型(如 intfloat)的值。
  • 拷贝引用类型
    • 拷贝的就是内存地址,相当于多个引用指向同一个对象,因此如果其中一个对象改变了这个内存地址的内容,就会影响到另一个对象。

深拷贝

深拷贝(Deep Copy)会另外创建一个一模一样的拷贝对象,这个拷贝对象跟原对象不共享内存,修改拷贝对象不会对原对象产生任何影响。深拷贝是拷贝了原对象的所有值,所以即使原对象的值发生了变化,拷贝对象的值也不会改变。

为什么需要深拷贝呢?

  • 避免共享引用
    • 当拷贝一个对象时,如果不使用深拷贝,那么拷贝的实际上是对原对象的引用,而不是真正的副本。这意味着对副本的任何修改都会影响到原对象。深拷贝则能确保拷贝的是对象的真正副本,与原对象没有引用关系。
  • 保证线程安全
    • 在多线程环境中,如果多个线程同时访问和修改同一个对象,可能会导致数据不一致和竞态条件。通过深拷贝创建对象的副本,每个线程都可以在自己的副本上进行操作,从而避免了线程安全问题。

Cloneable 接口

  • JDK 提供了 Cloneable 接口
1
2
3
public interface Cloneable {

}
  • Object 类有一个 clone() 方法
1
2
3
4
5
6
7
public class Object {

@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;

......
}
  • 如果某个类没有实现 Cloneable 接口就直接调用 clone() 方法,则会抛出 CloneNotSupportedException 异常
1
2
3
4
5
6
7
8
public class ObjectCopyDemo {

public static void main(String[] args) throws Exception {
ObjectCopyDemo demo = new ObjectCopyDemo();
Object clone = demo.clone();
}

}
1
2
3
Exception in thread "main" java.lang.CloneNotSupportedException: com.java.interview.base.ObjectCopyDemo
at java.base/java.lang.Object.clone(Native Method)
at com.java.interview.base.ObjectCopyDemo.main(ObjectCopyDemo.java:7)

浅拷贝的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class ShallowCopyDemo {

public static void main(String[] args) throws Exception {
Emp emp = new Emp("z3", 15, "雷军", "CEO");
System.out.println("原始对象:" + emp.getBoss().getTitle());

Emp emp2 = (Emp) emp.clone();
System.out.println("拷贝对象:" + emp2.getBoss().getTitle());

emp2.getBoss().setTitle("CTO");

System.out.println("------拷贝对象修改 title 为 CTO,是否会影响原始对象");
System.out.println("原始对象:" + emp.getBoss().getTitle());
System.out.println("拷贝对象:" + emp2.getBoss().getTitle());
}

@Data
@AllArgsConstructor
@NoArgsConstructor
private static class Boss implements Cloneable {

private String bossName;
private String title;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

@Data
@AllArgsConstructor
@NoArgsConstructor
private static class Emp implements Cloneable {

private String empName;
private Integer age;
private Boss boss;

public Emp(String empName, Integer age, String bossName, String title) {
this.empName = empName;
this.age = age;
this.boss = new Boss(bossName, title);
}

@Override
protected Object clone() throws CloneNotSupportedException {
// 调用 Object 类的 clone() 方法,以实现浅拷贝
return super.clone();
}
}

}

程序运行的输出结果为:

1
2
3
4
5
原始对象:CEO
拷贝对象:CEO
------拷贝对象修改 title 为 CTO,是否会影响原始对象
原始对象:CTO
拷贝对象:CTO

深拷贝的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class DeepCopyDemo {

public static void main(String[] args) throws Exception {
Emp emp = new Emp("z3", 15, "雷军", "CEO");
System.out.println("原始对象:" + emp.getBoss().getTitle());

Emp emp2 = (Emp) emp.clone();
System.out.println("拷贝对象:" + emp2.getBoss().getTitle());

emp2.getBoss().setTitle("CTO");

System.out.println("------拷贝对象修改 title 为 CTO,是否会影响原始对象");
System.out.println("原始对象:" + emp.getBoss().getTitle());
System.out.println("拷贝对象:" + emp2.getBoss().getTitle());
}

@Data
@AllArgsConstructor
@NoArgsConstructor
private static class Boss implements Cloneable {

private String bossName;
private String title;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

@Data
@AllArgsConstructor
@NoArgsConstructor
private static class Emp implements Cloneable {

private String empName;
private Integer age;
private Boss boss;

public Emp(String empName, Integer age, String bossName, String title) {
this.empName = empName;
this.age = age;
this.boss = new Boss(bossName, title);
}

@Override
protected Object clone() throws CloneNotSupportedException {
// 重新创建一个新的对象,以实现深拷贝
return new Emp(empName, age, boss.getBossName(), boss.getTitle());
}
}

}

程序运行的输出结果为:

1
2
3
4
5
原始对象:CEO
拷贝对象:CEO
------拷贝对象修改 title 为 CTO,是否会影响原始对象
原始对象:CEO
拷贝对象:CTO