【Java SE】深拷贝和浅拷贝
深拷贝和浅拷贝
在 Java 里,“拷贝对象”经常会遇到一个坑:拷贝出来的新对象,改了它的字段,结果原对象也跟着变了。根本原因通常就是 浅拷贝(Shallow Copy) 与 深拷贝(Deep Copy) 的区别。
什么是浅拷贝?(Shallow Copy)
浅拷贝:只复制对象本身的“第一层”数据。
- 基本类型(
int、double等)会直接复制值。 - 引用类型(如
List、自定义对象)只会复制“引用地址”,新旧对象会指向同一份内部数据。
“浅拷贝”的本质:只复制第一层字段,不复制引用对象内部结构。
浅拷贝示例(clone 方式)
class Address {
String city;
Address(String city) {
this.city = city;
}
}
class Person implements Cloneable {
String name; // 第一层字段(引用类型,但指向String不可变对象)
Address address; // 第一层字段(引用类型,指向可变Address对象)
Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone(); // 只做浅拷贝
}
}
public class ShallowCopyDemo {
public static void main(String[] args) throws Exception {
Address addr = new Address("Shanghai");
Person p1 = new Person("Alice", addr);
Person p2 = p1.clone(); // 浅拷贝
p2.name = "Bob"; // 改第一层字段:不影响 p1
p2.address.city = "Beijing"; // 改内部引用对象:会影响 p1
System.out.println(p1.name); // Alice
System.out.println(p1.address.city); // Beijing(被影响了)
System.out.println(p2.name); // Bob
System.out.println(p2.address.city); // Beijing
}
}
引用指向图

结论:浅拷贝拷贝出来的 p2.address 和 p1.address 指向同一个 Address 对象。
1) 类结构:谁是“第一层字段”,谁是“引用对象”
class Address {
String city;
}
class Person implements Cloneable {
String name; // 第一层字段(引用类型,但指向String不可变对象)
Address address; // 第一层字段(引用类型,指向可变Address对象)
}
Person里有两个字段:name和address- 浅拷贝只复制
Person这一层的字段值:name字段里存的是“指向某个 String 的引用”;address字段里存的是“指向某个 Address 的引用”
2) clone() 为啥是浅拷贝⭐
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone(); // 只做浅拷贝
}
//Person p2 = p1.clone();
进入 super.clone():调用的是 java.lang.Object#clone, Object.clone() 是一个 JVM 层面的原生(native)对象复制操作(实现不在 Java 代码里,在 JVM/运行时里)。做的是“按对象内存布局把实例字段整块拷贝”,而不是递归复制引用对象。
-
它会先做一个关键检查:当前对象是否实现了
Cloneable。实现了:允许克隆; 没实现:抛CloneNotSupportedException。 -
接下来,JVM 创建一个“同类型的新对象”,但是不会调用构造器,不会执行初始化逻辑,只是让 JVM 分配一块足够容纳
Person实例的内存,得到一个新的Person对象壳子。 -
然后 JVM 会把原对象的 实例字段 拷贝到新对象里,效果等价于:
- 基本类型字段:直接复制数值(
int/long/double…) - 引用类型字段:复制的是“引用值”(可以理解为指针/句柄),不会复制引用指向的对象本身
- 基本类型字段:直接复制数值(
-
【需要注意的是】,
clone()返回类型是Object,所以需要(Person)强转。
浅拷贝等价伪代码
Object.clone()(浅拷贝)的等价伪代码如下:
// 语义等价伪代码:java.lang.Object#clone()
Object Object_clone(Object thisObj) throws CloneNotSupportedException {
// A. 权限检查:必须实现 Cloneable
if (!(thisObj instanceof Cloneable)) {
throw new CloneNotSupportedException();
}
// B. 分配一个“同运行时类型”的新对象:不调用任何构造器
// (JVM 内部类似 Unsafe.allocateInstance)
Object newObj = VM.allocateInstance(thisObj.getClass()); // ctor NOT called
// C. 复制“实例字段”:按对象布局把内容拷贝过去(浅拷贝的根源)
// - 基本类型:复制值
// - 引用类型:复制引用值(地址/句柄),不会递归复制引用指向的对象
VM.copyInstanceFieldsBitwise(src = thisObj, dst = newObj);
return newObj;
}
把这段套到 Person 上,效果近似于:
Person p2 = VM.allocateInstance(Person.class); // 不跑 Person(...) 构造器
p2.name = p1.name; // 复制引用
p2.address = p1.address; // 复制引用(所以共享同一个 Address)
什么是深拷贝?(Deep Copy)
深拷贝:不仅复制对象本身,还会把对象内部所有引用对象也“递归地复制一份”。 因此拷贝后,新对象与原对象内部引用的数据互不影响。
深拷贝示例(手写 clone:对引用字段也 clone)
class Address implements Cloneable {
String city;
Address(String city) {
this.city = city;
}
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
class Person implements Cloneable {
String name;
Address address;
Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Person clone() throws CloneNotSupportedException {
Person copied = (Person) super.clone(); // 先浅拷贝第一层
copied.address = address.clone(); // 再深拷贝引用对象
return copied;
}
}
public class DeepCopyDemo {
public static void main(String[] args) throws Exception {
Person p1 = new Person("Alice", new Address("Shanghai"));
Person p2 = p1.clone(); // 深拷贝
p2.address.city = "Beijing";
System.out.println(p1.address.city); // Shanghai(不受影响)
System.out.println(p2.address.city); // Beijing
}
}
引用指向图

深拷贝等价伪代码
深拷贝版 clone():在“浅拷贝之后”手动再拷一层引用字段
class Address implements Cloneable {
String city;
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone(); // Address 里只有 String,够用了
}
}
class Person implements Cloneable {
String name;
Address address;
@Override
protected Person clone() throws CloneNotSupportedException {
Person copied = (Person) super.clone(); // 先做 JVM 字段拷贝(浅)
copied.address = address.clone(); // 再把可变引用对象复制一份(深)
return copied;
}
}
对应的“等价伪代码”就是:
Person deepClone(Person p1) {
Person p2 = shallowCloneByVM(p1); // 等价 super.clone()
p2.address = shallowCloneByVM(p1.address); // 再克隆引用对象
return p2;
}
什么时候必须用深拷贝?
一般当对象内部包含可变引用对象时,更需要深拷贝,例如:
- 字段里有
List/Map/ 数组 - 字段里有可变的自定义对象(如
Address) - 希望复制出“完全独立”的对象用于后续修改(例如 DTO 转换、缓存快照、回滚等)
如果内部引用对象是不可变的(例如 String、Integer、LocalDateTime 等不可变类型),浅拷贝的风险会小很多。
深拷贝的另一种常见方式:序列化 / 反序列化
除了手写 clone()/拷贝构造器外,序列化深拷贝也很常见:把对象(以及它引用到的整个对象图)写入字节流,再从字节流读回来。读回来的对象是全新的实例,一般能达到“深拷贝”的效果。
原生 Java 序列化实现深拷贝
前提:对象图中所有参与拷贝的类型都要实现 Serializable。
import java.io.*;
class Address implements Serializable {
private static final long serialVersionUID = 1L;
String city;
Address(String city) {
this.city = city;
}
}
class Person implements Serializable {
private static final long serialVersionUID = 1L;
String name;
Address address;
Person(String name, Address address) {
this.name = name;
this.address = address;
}
}
public class SerializationDeepCopyDemo {
public static <T extends Serializable> T deepCopyBySerialization(T obj) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(obj);
}
try (ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(bos.toByteArray()))) {
@SuppressWarnings("unchecked")
T copy = (T) ois.readObject();
return copy;
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("Deep copy by serialization failed", e);
}
}
public static void main(String[] args) {
Person p1 = new Person("Alice", new Address("Shanghai"));
Person p2 = deepCopyBySerialization(p1);
p2.address.city = "Beijing";
System.out.println(p1.address.city); // Shanghai(不受影响)
System.out.println(p2.address.city); // Beijing
// 验证不是同一个引用
System.out.println(p1.address == p2.address); // false
System.out.println(System.identityHashCode(p1.address));
System.out.println(System.identityHashCode(p2.address));
}
}
和手写深拷贝对比优缺点
- 优点
- 写起来省心:对复杂对象图(多层嵌套)不必手动逐层
new - 对“共享引用/循环引用”的对象图也通常能正确还原结构(仍保持引用关系)
- 写起来省心:对复杂对象图(多层嵌套)不必手动逐层
- 缺点
- 性能与内存开销大:序列化/反序列化 + 字节数组
- 侵入性:需要
Serializable,对象图里任意一个不可序列化都会直接失败(NotSerializableException) - 可维护性一般:类演进时要考虑
serialVersionUID、序列化兼容等问题
- 适用建议
- 偶尔拷贝、对象图复杂、对性能不敏感:可以用
- 高频拷贝/核心链路:更推荐拷贝构造器、手写深拷贝或专门的映射方案(按业务字段可控)
补充:关于 = 赋值
关于 = 赋值,不是拷贝
Person p2 = p1; // 只是引用指向同一个对象
这不是浅拷贝/深拷贝,而是“同一个对象两个引用”。
更多推荐




所有评论(0)