我刚工作时,天真地以为user2 = user1就是复制了一个新对象。结果当我修改user2的名字时,user1的名字也鬼使神差地变了!这次事故让我深刻理解了对象复制的两种方式:浅度复制和深度复制。
1. 引用赋值(=)的陷阱
- 代码演示:
User user1 = new User(1, "张三");
User user2 = user1; // 这不是复制!这只是让user2和user1指向内存中的同一个对象!
user2.setName("李四");
System.out.println(user1.getName()); // 输出什么?输出“李四”!user1和user2是同一个对象的两个“别名”,修改其中一个,另一个必然受影响。
2. 浅度复制(Shallow Copy)
概念: 创建一个新对象,然后将原对象的非静态字段值复制到新对象。
- 如果字段是基本类型,则复制其值。
- 如果字段是对象引用,则复制引用地址(而不是引用的对象本身)。因此,原对象和复制对象会共享这些子对象。
- 如何实现? 实现
Cloneable接口,重写clone()方法。
public class User implements Cloneable {
private Long id;
private String name;
private Address address; // 引用另一个对象
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); // 默认的clone是浅拷贝
}
}
// 使用
User user1 = new User(1, "张三", new Address("北京"));
User user2 = (User) user1.clone();
user2.setName("李四"); // 修改基本类型/String,不影响user1
user2.getAddress().setCity("上海"); // 修改引用类型,user1.getAddress().getCity() 也变成“上海”了!3. 深度复制(Deep Copy)
- 概念: 创建一个新对象,并递归地复制原对象所引用的所有对象。生成一个完全独立的副本。
- 如何实现? 手动在
clone()方法中复制所有引用对象。
public class User implements Cloneable {
private Long id;
private String name;
private Address address;
@Override
public Object clone() throws CloneNotSupportedException {
User cloned = (User) super.clone();
// 对address字段也进行复制
cloned.address = (Address) this.address.clone();
return cloned;
}
}- 其他方法: 通过序列化/反序列化(要求所有相关类都可序列化)、使用第三方库(如Apache Commons Lang的
SerializationUtils)也可以实现深度复制。
总结:
- 需要完全独立的副本时,用深度复制。(例如,需要修改副本而不影响原对象的所有层级)
- 如果对象内部只有基本类型和String,或者你明确希望共享内部对象,用浅度复制即可。
- 在实际开发中,我更喜欢通过构造器或专门的复制工具类来创建新对象,以避免
Cloneable接口的复杂性和局限性。