手动实现深拷贝需逐层创建新对象:基本类型直接赋值,引用类型调用复制构造函数或clone;序列化方式通用但要求所有类可序列化且有性能开销;第三方库如Apache Commons基于序列化封装,仍受相同限制。

手动实现深拷贝的核心是:不共享任何对象引用,所有嵌套对象都创建新实例并逐层复制数据。Java 本身不提供自动深拷贝机制,必须自己控制对象图的遍历和复制逻辑。
使用构造函数或工厂方法逐层复制
最直接的方式是为每个需要深拷贝的类编写专门的复制构造函数或静态工厂方法,显式地为每个字段(尤其是引用类型)创建新对象。
- 基本类型字段直接赋值
- 引用类型字段调用其对应的复制构造函数或 clone()(前提是它也支持深拷贝)
- 集合类需新建容器,并对每个元素递归深拷贝
例如:
public class Person {
private String name;
private Address address; // 引用类型
public Person(Person other) {
this.name = other.name; // String 不可变,直接赋值即可
this.address = (other.address != null) ? new Address(other.address) : null;
}
}
public class Address {
private String city;
private List<String> phones;
public Address(Address other) {
this.city = other.city;
this.phones = new ArrayList<>();
if (other.phones != null) {
for (String phone : other.phones) {
this.phones.add(new String(phone)); // String 也可直接 add,但体现思路
}
}
}
}
利用序列化实现通用深拷贝(需谨慎)
将对象序列化为字节数组再反序列化,能天然实现深拷贝——因为反序列化会重建整个对象图。但要求所有嵌套类都实现 Serializable,且不包含无法序列化的资源(如 Socket、Thread、数据库连接等)。
立即学习“Java免费学习笔记(深入)”;
- 优点:代码简洁,无需为每个类写复制逻辑
- 缺点:性能开销大;static/transient 字段不会被复制;可能抛出异常;破坏封装(如 readObject 中的逻辑)
- 注意:确保类及其所有字段类型都可序列化,否则运行时报 NotSerializableException
示例工具方法:
public static <T> T deepCopy(T obj) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
@SuppressWarnings("unchecked")
T copy = (T) ois.readObject();
ois.close();
return copy;
}
借助第三方库简化(如 Apache Commons Lang)
Apache Commons Lang 提供 SerializationUtils.clone(),底层就是基于序列化的封装,用法更简洁,但仍受序列化限制。
- Maven 依赖:
org.apache.commons:commons-lang3 - 调用方式:
Person copy = SerializationUtils.clone(original); - 注意:仍要求对象图中所有类实现 Serializable
避免踩坑的关键细节
手动深拷贝容易忽略边界情况,导致“伪深拷贝”(部分引用仍共享):
-
数组要 new 并逐个复制:不能只 clone() 数组引用,要用
Arrays.copyOf()或循环赋值 - 不可变对象可直接引用:如 String、Integer 等,无需 new,但需确认其真正不可变(自定义类勿盲目假设)
- 处理循环引用:若对象图存在 A→B→A,手动递归复制会栈溢出;需用 Map 缓存已复制对象的映射关系
- 注意 final 字段:若引用类型字段被声明为 final,构造函数中必须初始化,不能在复制后修改
不复杂但容易忽略。


















