一、对象拷贝的核心背景
1. 为什么需要 "拷贝"?
Java 中直接赋值对象(如 User u2 = u1)属于引用传递,新引用仅指向原对象的内存地址,并非创建新对象:
java
运行
// 直接赋值:引用共享,修改一个会影响另一个
User u1 = new User("张三", 20);
User u2 = u1;
u2.setName("李四");
System.out.println(u1.getName()); // 输出:李四(u1被意外修改)
拷贝的核心目的:创建独立的新对象,新对象与原对象初始值相同,但后续修改互不影响。
2. 拷贝的核心分类
表格
| 类型 | 核心特征 |
|---|---|
| 浅拷贝 | 仅拷贝对象表层,引用类型字段共享内存 |
| 深拷贝 | 拷贝对象及所有嵌套子对象,所有字段独立 |
二、浅拷贝(Shallow Copy):仅拷贝 "表层"
1. 定义
创建新对象后:
- 基本数据类型字段:直接复制值;
- 引用数据类型字段:仅复制引用(仍指向原对象的内存地址)。简单说:浅拷贝只拷贝 "对象本身",不拷贝对象里的 "子对象"。
2. 实现方式(核心)
方式:实现 Cloneable 接口 + 重写 clone() 方法
Cloneable 是标记接口(无任何方法),仅表示类支持拷贝;Object 类的 clone() 方法默认实现浅拷贝。
步骤 1:定义嵌套对象结构
java
运行
// 地址类(引用类型)
class Address {
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
// getter/setter 省略
@Override
public String toString() {
return "Address{" + "city='" + city + '\'' + ", street='" + street + '\'' + '}';
}
}
// 用户类(实现 Cloneable 支持拷贝)
class User implements Cloneable {
// 基本数据类型
private String name;
private int age;
// 引用数据类型(子对象)
private Address address;
public User(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
// 重写 clone 方法实现浅拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
// 调用父类 Object 的 clone(),默认浅拷贝
return super.clone();
}
// getter/setter、toString 省略
}
步骤 2:测试浅拷贝效果
java
运行
public class ShallowCopyDemo {
public static void main(String[] args) throws CloneNotSupportedException {
// 1. 创建原对象
Address address = new Address("北京", "朝阳区");
User user1 = new User("张三", 20, address);
// 2. 浅拷贝得到新对象
User user2 = (User) user1.clone();
// 3. 修改基本类型字段:不影响原对象
user2.setName("李四");
user2.setAge(22);
System.out.println("原对象user1:" + user1);
// 输出:User{name='张三', age=20, address=Address{city='北京', street='朝阳区'}}
System.out.println("拷贝对象user2:" + user2);
// 输出:User{name='李四', age=22, address=Address{city='北京', street='朝阳区'}}
// 4. 修改引用类型字段(子对象):影响原对象
user2.getAddress().setCity("上海");
System.out.println("修改子对象后user1:" + user1);
// 输出:User{name='张三', age=20, address=Address{city='上海', street='朝阳区'}}
System.out.println("修改子对象后user2:" + user2);
// 输出:User{name='李四', age=22, address=Address{city='上海', street='朝阳区'}}
}
}
3. 核心特征
表格
| 字段类型 | 拷贝方式 | 是否独立 |
|---|---|---|
| 基本数据类型 | 复制值 | 是 |
| 引用数据类型 | 复制引用(指向原对象) | 否 |
4. 适用场景
- 对象仅包含基本数据类型字段;
- 无需修改引用类型字段,或有意共享子对象(如只读数据)。
三、深拷贝(Deep Copy):拷贝 "所有层级"
1. 定义
创建新对象后,不仅拷贝对象本身,还递归拷贝所有引用类型的子对象。简单说:深拷贝创建 "完全独立" 的新对象,原对象和拷贝对象的所有字段(包括子对象)互不影响。
2. 实现方式(两种核心方式)
方式 1:重写 clone() 方法(递归拷贝子对象)
核心思路 :让所有嵌套的引用类型类都实现 Cloneable,在顶层对象的 clone() 中递归拷贝子对象。
步骤 1:改造嵌套类和顶层类
java
运行
// 地址类实现 Cloneable
class Address implements Cloneable {
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
// 地址类重写 clone 方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
// getter/setter、toString 省略
}
// 用户类改造 clone 方法(递归拷贝子对象)
class User implements Cloneable {
private String name;
private int age;
private Address address;
public User(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// 1. 先拷贝当前对象(浅拷贝)
User cloneUser = (User) super.clone();
// 2. 手动拷贝引用类型字段(递归深拷贝)
cloneUser.address = (Address) this.address.clone();
return cloneUser;
}
// getter/setter、toString 省略
}
步骤 2:测试深拷贝效果
java
运行
public class DeepCopyDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("北京", "朝阳区");
User user1 = new User("张三", 20, address);
User user2 = (User) user1.clone();
// 修改子对象:原对象不受影响
user2.getAddress().setCity("上海");
System.out.println("原对象user1:" + user1);
// 输出:User{name='张三', age=20, address=Address{city='北京', street='朝阳区'}}
System.out.println("拷贝对象user2:" + user2);
// 输出:User{name='张三', age=20, address=Address{city='上海', street='朝阳区'}}
}
}
方式 2:序列化 / 反序列化(推荐,适配复杂嵌套)
核心优势:无需手动递归拷贝,适配多层嵌套对象(如 User→Address→House→Room)。
步骤 1:所有嵌套类实现 Serializable 接口
java
运行
import java.io.*;
// 地址类实现 Serializable
class Address implements Serializable {
// 序列化版本号(避免反序列化异常)
private static final long serialVersionUID = 1L;
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
// getter/setter 省略
}
// 用户类实现 Serializable
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private Address address;
public User(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
// 通用深拷贝方法
public User deepCopy() throws IOException, ClassNotFoundException {
// 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 (User) ois.readObject();
}
// getter/setter 省略
}
步骤 2:测试序列化深拷贝
java
运行
public class SerializeCopyDemo {
public static void main(String[] args) throws Exception {
Address address = new Address("北京", "朝阳区");
User user1 = new User("张三", 20, address);
User user2 = user1.deepCopy();
user2.getAddress().setCity("上海");
System.out.println(user1.getAddress().getCity()); // 输出:北京(原对象无变化)
}
}
3. 核心特征
表格
| 字段类型 | 拷贝方式 | 是否独立 |
|---|---|---|
| 基本数据类型 | 复制值 | 是 |
| 引用数据类型 | 递归拷贝子对象(新内存) | 是 |
4. 适用场景
- 对象包含多层嵌套的引用类型字段;
- 需要完全隔离原对象和拷贝对象(如多线程操作、数据备份、核心业务数据修改)。
四、浅拷贝 vs 深拷贝:核心对比
表格
| 维度 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 拷贝范围 | 仅拷贝对象本身,不拷贝子对象 | 拷贝对象 + 所有层级的子对象 |
| 引用类型字段 | 共享同一块内存 | 拥有独立的内存空间 |
| 性能 | 速度快,内存开销小 | 速度慢,内存开销大(递归 / 序列化) |
| 实现复杂度 | 简单(默认 clone () 即可) | 复杂(递归 clone / 序列化) |
| 数据独立性 | 基本类型独立,引用类型共享 | 完全独立 |
五、避坑指南:常见错误与最佳实践
1. 常见错误
- 误以为
clone()方法默认是深拷贝(实际是浅拷贝); - 嵌套对象未实现
Cloneable/Serializable,导致深拷贝失败; - 对不可变对象(如 String、Integer)过度使用深拷贝(无意义,不可变对象本身无法修改)。
2. 最佳实践
(1)选型原则
- 简单对象(仅基本类型):用浅拷贝(
clone()),高效; - 复杂嵌套对象:用序列化实现深拷贝(通用、易维护);
- 避免手动递归拷贝(易漏写子对象,维护成本高)。
(2)第三方工具简化(推荐)
使用 Apache Commons Lang 的 SerializationUtils.clone() 简化序列化拷贝:
步骤 1:引入依赖
xml
<!-- Maven 依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
步骤 2:一行实现深拷贝
java
运行
// 前提:目标类及所有嵌套类实现 Serializable
User user2 = SerializationUtils.clone(user1);
六、核心总结
- 拷贝的核心是 "数据独立性":需要完全隔离原对象和拷贝对象用深拷贝,仅表层隔离用浅拷贝;
- 浅拷贝仅拷贝对象本身,引用类型字段共享内存,适用于简单对象或只读共享场景;
- 深拷贝递归拷贝所有子对象,数据完全独立,复杂嵌套场景优先用序列化(或第三方工具)实现,避免手动递归的繁琐和错误。