我刚工作时,天真地以为user2 = user1就是复制了一个新对象。结果当我修改user2的名字时,user1的名字也鬼使神差地变了!这次事故让我深刻理解了对象复制的两种方式:浅度复制和深度复制。

1. 引用赋值(=)的陷阱

  • 代码演示:
User user1 = new User(1, "张三");
User user2 = user1; // 这不是复制!这只是让user2和user1指向内存中的同一个对象!

user2.setName("李四");
System.out.println(user1.getName()); // 输出什么?输出“李四”!
  • user1user2是同一个对象的两个“别名”,修改其中一个,另一个必然受影响。

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接口的复杂性和局限性。
如果觉得我的文章对你有用,请随意赞赏