Java Object.clone() 浅拷贝与深拷贝全解析
浅拷贝是Object类clone()方法的默认实现逻辑,克隆时仅对对象的表层成员对基本数据类型(int/boolean等)和不可变引用类型(String/Integer等包装类):直接复制值(包装类因不可变,修改时会创建新对象,天然隔离);对可变引用类型成员(自定义类/集合等):仅复制内存地址,克隆对象与原对象的引用成员指向堆内存中的同一个实际对象。// 可变引用类型:修改detail属性会改变对
本文基于Object类clone()方法,全面梳理浅拷贝(Shallow Clone)和深拷贝(Deep Clone)的核心定义、实现方法、本质区别、适用场景,涵盖clone()原生实现、手动扩展深拷贝、替代方案等所有核心内容,同时明确各类实现的要求与注意事项,最终通过总结提炼关键要点。
一、前置基础:Object.clone() 原生特性与克隆前提
1. clone() 方法基础信息
clone()是Object类的native原生方法,底层由C/C++实现内存直接拷贝,效率高于手动new对象赋值,核心作用是创建当前对象的独立副本(克隆对象与原对象堆内存地址不同),方法声明如下:
// protected修饰 + 抛出受检异常CloneNotSupportedException
protected native Object clone() throws CloneNotSupportedException;
2. 实现对象克隆的两个必要前提(缺一不可)
前提1:实现Cloneable标记接口
Cloneable是空接口(无任何抽象方法),仅作为JVM的克隆许可标记——告知JVM该类允许被克隆。若未实现此接口,调用clone()会直接抛出CloneNotSupportedException。
前提2:重写clone()并修改访问权限为public
Object类中clone()为protected修饰,结合Java权限规则:
- 同包类可直接调用,不同包子类仅能通过
super.clone()调用,外部类无法直接使用; - 重写时需将访问权限改为
public,并建议使用协变返回值(将返回值从Object改为当前类类型),避免强制类型转换,提升代码可读性。
3. 基础克隆示例(原生浅拷贝)
public class Person implements Cloneable {
private String name; // 基本类型包装类(不可变,按基本类型处理)
private int age; // 基本数据类型
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 重写clone():public + 协变返回值 + 调用super.clone()
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
// getter/setter + toString
public void setName(String name) { this.name = name; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
// 测试:克隆对象与原对象独立
public class BaseCloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person("张三", 20);
Person p2 = p1.clone();
System.out.println(p1 == p2); // false:堆地址不同,独立对象
p2.setName("李四");
System.out.println(p1); // Person{name='张三', age=20}(修改副本不影响原对象)
System.out.println(p2); // Person{name='李四', age=20}
}
}
二、浅拷贝(Shallow Clone)—— Object.clone() 原生实现
1. 核心定义
浅拷贝是Object类clone()方法的默认实现逻辑,克隆时仅对对象的表层成员进行拷贝:
- 对基本数据类型(int/boolean等)和不可变引用类型(String/Integer等包装类):直接复制值(包装类因不可变,修改时会创建新对象,天然隔离);
- 对可变引用类型成员(自定义类/集合等):仅复制内存地址,克隆对象与原对象的引用成员指向堆内存中的同一个实际对象。
2. 核心特征
- 表层独立,深层共享:克隆对象与原对象本身是独立的(地址不同),但二者的可变引用类型成员共享同一个对象;
- 修改联动:修改克隆对象中可变引用类型成员的属性,会同步影响原对象(反之亦然),这是浅拷贝的核心坑点;
- 实现简单:仅需满足克隆两个前提,直接调用
super.clone()即可,无需额外代码。
3. 完整实现代码(含问题演示)
步骤1:定义可变引用类型成员类(自定义地址类)
// 可变引用类型:修改detail属性会改变对象本身
class Address {
private String detail; // 如"北京市海淀区"
public Address(String detail) {
this.detail = detail;
}
// getter/setter + toString
public void setDetail(String detail) { this.detail = detail; }
@Override
public String toString() {
return "Address{detail='" + detail + "'}";
}
}
步骤2:定义包含引用成员的主类,实现浅拷贝
public class Person implements Cloneable {
private String name; // 不可变引用类型(String)
private int age; // 基本数据类型
private Address addr; // 可变引用类型(自定义类,核心坑点)
public Person(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
// 浅拷贝:直接调用super.clone(),原生实现
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
// getter/setter + toString
public Address getAddr() { return addr; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", addr=" + addr + "}";
}
}
步骤3:测试浅拷贝的联动修改问题
public class ShallowCloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
// 1. 创建原对象及引用成员
Address addr = new Address("北京市海淀区");
Person p1 = new Person("张三", 20, addr);
// 2. 浅克隆得到副本
Person p2 = p1.clone();
// 3. 修改克隆对象的可变引用成员属性
p2.getAddr().setDetail("上海市浦东新区");
// 4. 结果:原对象与克隆对象的addr属性同步被修改!
System.out.println("原对象:" + p1); // 原对象:Person{name='张三', age=20, addr=Address{detail='上海市浦东新区'}}
System.out.println("克隆对象:" + p2); // 克隆对象:Person{name='张三', age=20, addr=Address{detail='上海市浦东新区'}}
}
}
4. 浅拷贝的适用场景
仅适用于无可变引用类型成员的简单类(如仅包含基本类型/包装类/String),此类场景下浅拷贝能保证克隆对象与原对象完全隔离,且实现成本最低。
三、深拷贝(Deep Clone)—— 手动扩展的完全隔离拷贝
1. 核心定义
深拷贝是对浅拷贝的升级扩展,克隆时会递归拷贝对象的所有层级成员:
- 对基本类型/不可变引用类型:与浅拷贝一致,直接复制值;
- 对所有可变引用类型成员:不仅复制地址,还会创建该引用成员的全新实例,并递归拷贝其内部的引用成员;
最终克隆对象与原对象在堆内存中形成两个完全独立的对象树,二者无任何内存共享,修改其一完全不影响另一。
2. 核心特征
- 完全隔离:克隆对象与原对象(包括所有层级的引用成员)均为独立对象,无任何内存共享;
- 修改无联动:无论修改哪一层级的成员属性,都不会对另一个对象产生任何影响;
- 需手动实现:无法通过
super.clone()直接实现,需根据场景编写额外代码,保证所有引用成员都被深度克隆。
3. 两种核心实现方法(含完整代码+要求)
深拷贝有两种主流实现方式,分别适用于引用层级简单和**引用层级复杂(多层嵌套/集合)**的场景,均需满足基础克隆前提(实现Cloneable+重写clone()),且对引用成员有额外要求。
方法1:重写clone()实现递归克隆(推荐,适用于简单层级)
核心思路
- 先调用
super.clone()完成当前对象的浅拷贝,得到基础克隆对象; - 对类中每一个可变引用类型成员,单独调用其
clone()方法(需保证该引用成员类也实现Cloneable+重写clone()); - 将克隆后的引用成员重新赋值给基础克隆对象,替换原有共享的引用地址,实现深度隔离。
实现要求
- 主类:实现
Cloneable+重写clone()(public+协变返回值); - 所有可变引用类型成员类:必须依次实现
Cloneable+重写clone(),支持递归克隆(多层嵌套则需逐层实现)。
完整代码实现
步骤1:引用成员类(Address)实现克隆前提,支持自身克隆
class Address implements Cloneable {
private String detail;
public Address(String detail) {
this.detail = detail;
}
// 关键:引用成员类必须重写clone(),改为public
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
// getter/setter + toString
public void setDetail(String detail) { this.detail = detail; }
@Override
public String toString() {
return "Address{detail='" + detail + "'}";
}
}
步骤2:主类(Person)重写clone(),实现递归深克隆
public class Person implements Cloneable {
private String name;
private int age;
private Address addr; // 可变引用成员
public Person(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
// 深拷贝:浅拷贝基础上,递归克隆引用成员
@Override
public Person clone() throws CloneNotSupportedException {
// 1. 先完成自身的浅拷贝,得到基础克隆对象
Person clonePerson = (Person) super.clone();
// 2. 递归克隆引用成员Address,替换原有共享引用(核心步骤)
clonePerson.addr = this.addr.clone();
// 3. 若有多个引用成员,依次递归克隆并赋值
return clonePerson;
}
// getter/setter + toString
public Address getAddr() { return addr; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", addr=" + addr + "}";
}
}
步骤3:测试深拷贝——完全隔离,无联动修改
public class DeepCloneByRecursionTest {
public static void main(String[] args) throws CloneNotSupportedException {
Address addr = new Address("北京市海淀区");
Person p1 = new Person("张三", 20, addr);
Person p2 = p1.clone(); // 深克隆
// 修改克隆对象的引用成员属性
p2.getAddr().setDetail("上海市浦东新区");
// 结果:原对象与克隆对象完全独立,互不影响
System.out.println("原对象:" + p1); // 原对象:Person{name='张三', age=20, addr=Address{detail='北京市海淀区'}}
System.out.println("克隆对象:" + p2); // 克隆对象:Person{name='张三', age=20, addr=Address{detail='上海市浦东新区'}}
}
}
方法2:序列化+反序列化实现(适用于复杂层级/集合)
核心思路
利用Java序列化机制,将原对象转化为字节流写入内存,再将字节流反序列化为全新对象——反序列化的过程会自动为所有层级的引用成员创建全新实例,天然实现深拷贝,无需手动递归克隆。
实现要求
- 所有涉及的类(主类+所有层级的可变引用成员类+集合泛型类):必须实现
Serializable标记接口(空接口,标记类可序列化); - 建议为每个序列化类显式声明
serialVersionUID:避免类结构(如新增/删除字段)修改后,反序列化时抛出InvalidClassException(序列化版本号不匹配); - 若类中有不可序列化的成员(如
transient修饰的变量),反序列化时该成员会被赋值为默认值(基本类型0/布尔false,引用类型null)。
完整代码实现
步骤1:所有类实现Serializable,声明序列化版本号
import java.io.Serializable;
// 引用成员类:实现Serializable + 显式serialVersionUID
class Address implements Serializable {
// 序列化版本号,建议显式声明(值可自定义,如1L)
private static final long serialVersionUID = 1L;
private String detail;
public Address(String detail) {
this.detail = detail;
}
// getter/setter + toString
public void setDetail(String detail) { this.detail = detail; }
@Override
public String toString() {
return "Address{detail='" + detail + "'}";
}
}
// 主类:实现Serializable + 显式serialVersionUID
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private Address addr; // 可变引用成员(已实现序列化)
public Person(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
// 核心:序列化+反序列化实现深克隆(无需实现Cloneable)
public Person deepCloneBySerialize() throws Exception {
// 1. 序列化:将对象写入字节流(内存中)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this); // 写入当前对象及所有层级成员
// 2. 反序列化:从字节流读取全新对象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Person) ois.readObject(); // 返回深度克隆的新对象
}
// getter/setter + toString
public Address getAddr() { return addr; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", addr=" + addr + "}";
}
}
步骤2:测试序列化深拷贝——支持任意复杂层级
public class DeepCloneBySerializeTest {
public static void main(String[] args) {
try {
Address addr = new Address("北京市海淀区");
Person p1 = new Person("张三", 20, addr);
Person p2 = p1.deepCloneBySerialize(); // 序列化深克隆
// 修改克隆对象的深层引用成员
p2.getAddr().setDetail("广州市天河区");
// 结果:完全隔离,无任何联动
System.out.println("原对象:" + p1); // 原对象:Person{name='张三', age=20, addr=Address{detail='北京市海淀区'}}
System.out.println("克隆对象:" + p2); // 克隆对象:Person{name='张三', age=20, addr=Address{detail='广州市天河区'}}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. 深拷贝的适用场景
适用于包含可变引用类型成员的类,尤其是:
- 引用成员为自定义类(如Address、Order等);
- 引用成员为集合(如List/Map,存储自定义对象);
- 引用成员存在多层嵌套(如Person→Order→Goods,多层引用)。
四、浅拷贝与深拷贝的核心区别(全维度对比表)
| 对比维度 | 浅拷贝(Shallow Clone) | 深拷贝(Deep Clone) |
|---|---|---|
| 实现基础 | Object.clone() 原生实现,直接调用super.clone() | 基于浅拷贝扩展/序列化,需手动编写额外代码 |
| 拷贝范围 | 仅拷贝对象表层成员,可变引用类型仅复制地址 | 递归拷贝所有层级成员,可变引用类型创建新实例 |
| 内存关系 | 克隆对象与原对象共享可变引用成员 | 克隆对象与原对象完全无内存共享(独立对象树) |
| 修改影响 | 修改克隆对象的可变引用成员,同步影响原对象 | 修改任意成员,对原对象无任何影响 |
| 实现难度 | 简单,仅需满足克隆两个前提 | 较复杂,需递归克隆/序列化,依赖引用成员配合 |
| 对引用成员的要求 | 无额外要求(无需实现Cloneable/Serializable) | 递归克隆:引用成员需实现Cloneable+重写clone() 序列化:所有类需实现Serializable |
| 效率 | 高(native方法直接内存拷贝,无额外开销) | 较低(递归克隆有方法调用开销,序列化有IO/反射开销) |
| 适用场景 | 无可变引用类型的简单类 | 包含可变引用类型/多层嵌套/集合的复杂类 |
| final成员兼容 | 兼容(仅复制地址,无需修改final引用) | 递归克隆:不兼容final可变引用成员(无法替换引用) 序列化:兼容(自动创建新实例赋值) |
五、深拷贝的替代方案:拷贝构造器(实际开发首选)
由于clone()实现深拷贝存在实现繁琐、final成员限制、引用成员配合要求高等问题,实际开发中更推荐使用拷贝构造器实现对象拷贝,无需依赖Cloneable/Serializable接口,灵活控制浅/深拷贝,且代码可读性更高。
1. 拷贝构造器定义
在类中定义一个参数为当前类对象的构造器,在构造器中手动复制原对象的所有成员变量:
- 基本类型/不可变引用类型:直接复制值;
- 可变引用类型:可选择直接复制地址(浅拷贝)或创建新实例(深拷贝),灵活控制,无需依赖引用成员的克隆/序列化实现。
2. 完整代码实现(灵活实现深拷贝)
// 引用成员类:无需实现任何标记接口
class Address {
private String detail;
public Address(String detail) {
this.detail = detail;
}
// 可选:为引用成员添加拷贝构造器,方便主类深拷贝
public Address(Address original) {
this.detail = original.detail;
}
// getter/setter + toString
public void setDetail(String detail) { this.detail = detail; }
@Override
public String toString() {
return "Address{detail='" + detail + "'}";
}
}
// 主类:通过拷贝构造器实现深拷贝
public class Person {
private String name;
private int age;
private Address addr;
// 普通构造器
public Person(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
// 核心:拷贝构造器(实现深拷贝)
public Person(Person original) {
// 基本类型/不可变引用类型:直接复制值
this.name = original.name;
this.age = original.age;
// 可变引用类型:创建新实例(深拷贝),浅拷贝则直接this.addr = original.addr
this.addr = new Address(original.addr);
}
// getter/setter + toString
public Address getAddr() { return addr; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", addr=" + addr + "}";
}
}
// 测试:拷贝构造器深拷贝,完全隔离
public class CopyConstructorTest {
public static void main(String[] args) {
Address addr = new Address("北京市海淀区");
Person p1 = new Person("张三", 20, addr);
Person p2 = new Person(p1); // 调用拷贝构造器,实现深拷贝
p2.getAddr().setDetail("深圳市南山区");
System.out.println("原对象:" + p1); // 原对象:Person{name='张三', age=20, addr=Address{detail='北京市海淀区'}}
System.out.println("克隆对象:" + p2); // 克隆对象:Person{name='张三', age=20, addr=Address{detail='深圳市南山区'}}
}
}
3. 拷贝构造器的核心优势
- 无接口依赖:无需实现Cloneable/Serializable,避免接口约束;
- 无异常处理:无需捕获CloneNotSupportedException/IO异常,代码更简洁;
- 灵活可控:手动控制每个成员的拷贝方式,浅/深拷贝自由切换;
- 兼容final成员:可直接为final可变引用成员创建新实例赋值,无clone()的final限制;
- 可读性高:代码逻辑直观,无需理解递归克隆/序列化的底层细节。
六、关键注意事项(避坑指南)
- 不可变引用类型无浅拷贝问题:String、Integer、Long等包装类是不可变类,修改其值时会创建新对象,因此即使浅拷贝,克隆对象与原对象的该成员也天然隔离,无需深度克隆;
- final可变引用成员的限制:若类中有
final修饰的可变引用成员,无法通过递归克隆实现深拷贝(final引用赋值后不可修改,无法替换为克隆后的新引用),此时推荐使用序列化或拷贝构造器; - 集合的克隆注意事项:Java集合(如ArrayList/HashMap)的
clone()方法也是浅拷贝——仅复制集合本身,集合中的元素仍为引用共享,若需集合深拷贝,需遍历元素并逐个克隆/新建; - transient关键字的影响:序列化实现深拷贝时,
transient修饰的成员会被跳过,反序列化时赋值为默认值,若需保留该成员,请勿使用transient; - native方法的效率优势:Object.clone()是native方法,直接操作内存拷贝,效率高于拷贝构造器,但实际开发中,拷贝构造器的灵活性远大于效率差异,因此仍是首选;
- 克隆对象的equals()重写:若需判断克隆对象与原对象的内容是否一致,需重写
equals()方法(默认比较地址),否则p1.equals(p2)始终返回false。
七、核心总结
- 克隆的基础前提:使用Object.clone()实现拷贝,必须实现Cloneable接口+重写clone()并改为public,否则会抛出异常且无法外部调用;
- 浅拷贝核心:Object.clone()原生实现,仅拷贝表层成员,可变引用类型共享地址,修改联动,适用于无可变引用的简单类,实现简单、效率高;
- 深拷贝核心:完全隔离原对象与克隆对象,递归拷贝所有层级引用成员,修改无联动,适用于含可变引用/多层嵌套的复杂类,需手动实现(递归克隆/序列化),对引用成员有额外要求;
- 浅深拷贝核心区别:是否为可变引用类型创建新实例——浅拷贝仅复制地址,深拷贝创建新实例,这是二者所有差异的根源;
- 深拷贝实现选择:简单层级用递归克隆,复杂层级/集合用序列化,实际开发首选拷贝构造器(灵活、无约束、兼容性强);
- 关键避坑点:不可变类无浅拷贝问题,final可变引用成员避免使用递归克隆,集合clone()仍是浅拷贝;
- clone()的定位:Java原生的高效拷贝方案,但使用约束多、坑点多,实际开发中若非追求极致效率,优先选择更灵活的拷贝构造器。
简单来说:浅拷贝是“表层复制,深层共享”,深拷贝是“全量复制,完全隔离”,二者的选择取决于类中是否包含可变引用类型成员,而拷贝构造器则是平衡“实现难度、灵活性、兼容性”的最优解。
更多推荐


所有评论(0)