抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

深拷贝和浅拷贝是面试常考问题。本文通过图解和代码示例,彻底搞清楚它们的区别以及如何正确实现深拷贝。

概念区分

浅拷贝(Shallow Copy)

创建一个新对象,但新对象的字段直接复制原对象字段的值:

  • 基本类型:复制值本身
  • 引用类型:复制引用地址(指向同一个对象)

深拷贝(Deep Copy)

创建一个新对象,并且递归复制所有引用类型字段指向的对象,使新旧对象完全独立。

图解区别

假设有一个 Person 对象,包含一个 Address 引用:

1
2
3
4
5
6
原对象 person1:
┌─────────────┐ ┌─────────────┐
│ name: "Tom" │ │ city: "北京" │
│ age: 25 │──────▶│ street: "X" │
│ address ────│ └─────────────┘
└─────────────┘ Address对象

浅拷贝后

1
2
3
4
5
6
7
8
9
10
11
12
13
person1:
┌─────────────┐ ┌─────────────┐
│ name: "Tom" │ │ city: "北京" │
│ age: 25 │──┬───▶│ street: "X" │
│ address ────│ │ └─────────────┘
└─────────────┘ │ 同一个Address!

person2: │
┌─────────────┐ │
│ name: "Tom" │ │
│ age: 25 │──┘
│ address ────│
└─────────────┘

修改 person2.address.city 会影响 person1

深拷贝后

1
2
3
4
5
6
7
8
9
10
11
12
13
person1:
┌─────────────┐ ┌─────────────┐
│ name: "Tom" │ │ city: "北京" │
│ age: 25 │──────▶│ street: "X" │
│ address ────│ └─────────────┘
└─────────────┘ Address对象1

person2:
┌─────────────┐ ┌─────────────┐
│ name: "Tom" │ │ city: "北京" │
│ age: 25 │──────▶│ street: "X" │
│ address ────│ └─────────────┘
└─────────────┘ Address对象2(独立的)

两个对象完全独立,互不影响。

代码示例

定义类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Address {
String city;
String street;

Address(String city, String street) {
this.city = city;
this.street = street;
}
}

class Person implements Cloneable {
String name;
int age;
Address address;

Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
}

浅拷贝实现

1
2
3
4
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone(); // 默认是浅拷贝
}

验证浅拷贝的问题:

1
2
3
4
5
6
7
8
Person p1 = new Person("Tom", 25, new Address("北京", "长安街"));
Person p2 = p1.clone();

p2.name = "Jerry"; // 修改基本类型,不影响p1
p2.address.city = "上海"; // 修改引用对象,p1也被影响!

System.out.println(p1.name); // Tom(不受影响)
System.out.println(p1.address.city); // 上海(被改了!)

深拷贝实现

方法1:手动递归拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Address implements Cloneable {
// ...
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}

class Person implements Cloneable {
// ...
@Override
protected Person clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.address = this.address.clone(); // 手动深拷贝引用字段
return cloned;
}
}

方法2:序列化反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
public Person deepClone() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Person) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

注意:所有相关类必须实现 Serializable 接口。

方法3:JSON序列化

1
2
3
4
5
6
7
8
9
10
// 使用Jackson或Gson
public Person deepClone() {
ObjectMapper mapper = new ObjectMapper();
try {
String json = mapper.writeValueAsString(this);
return mapper.readValue(json, Person.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

方法4:拷贝构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
// 拷贝构造函数
public Person(Person other) {
this.name = other.name;
this.age = other.age;
this.address = new Address(other.address); // 创建新的Address
}
}

class Address {
public Address(Address other) {
this.city = other.city;
this.street = other.street;
}
}

各种类型的拷贝行为

类型 浅拷贝行为 需要深拷贝?
基本类型 (int, double等) 复制值 不需要
String 复制引用,但String不可变,安全 不需要
数组 复制引用 需要
集合 (List, Map等) 复制引用 需要
自定义对象 复制引用 需要

数组的深拷贝

1
2
3
4
5
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1.clone(); // 基本类型数组,clone是深拷贝

Person[] arr3 = {new Person(...)};
Person[] arr4 = arr3.clone(); // 对象数组,clone是浅拷贝!元素还是同一个

集合的深拷贝

1
2
3
4
5
6
7
8
9
10
List<Person> list1 = new ArrayList<>();
list1.add(new Person("Tom", 25, new Address("北京", "X")));

// 浅拷贝
List<Person> list2 = new ArrayList<>(list1);

// 深拷贝
List<Person> list3 = list1.stream()
.map(Person::clone)
.collect(Collectors.toList());

总结

特性 浅拷贝 深拷贝
基本类型字段 独立 独立
引用类型字段 共享 独立
实现复杂度 简单 复杂
性能
使用场景 对象只有基本类型字段 需要完全独立的副本

选择建议:

  • 如果对象只包含基本类型和不可变对象(如String),浅拷贝足够
  • 如果对象包含可变引用类型,且需要独立修改,使用深拷贝
  • 推荐使用拷贝构造函数,代码清晰且类型安全