[Java实战履历]对象拷贝

铁佛  论坛元老 | 2025-4-16 10:24:10 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1830|帖子 1830|积分 5490

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
首先,对于不可变的类,我们不应该实现Cloneable接口,因为不可变类不需要拷贝,直接引用即可,实现Cloneable接口只会造成浪费。
对于Java可变类来说,拷贝分为浅拷贝与深拷贝。


  • 浅拷贝(Shallow Copy)
    只复制第一层,嵌套对象共享引用,适合简单场景。常见的Object.clone()方法就是浅拷贝。
  • 深拷贝(Deep Copy)
    递归复制所有层,完全独立,适合复杂数据。一般需要手动实现或使用第三方依赖。
谨慎重写clone方法

Cloneable是一个标记接口(marker interface),没有方法声明。clone方法定义在Object类中,且是protected,需要子类显式重写为public。
Object.clone()默认实验浅拷贝,不实用于需要深拷贝的场景。如果重写clone()来支持深拷贝会怎么样呢?会不会有标题?
重写clone()支持深拷贝带来的标题



  • 代码复杂,维护成本高
    必须确保嵌套对象和对象的元素支持clone,需要递归处理所有字段。
    嵌套多层时逐层克隆容易出错且难以维护、耦合高。
  • 不可变字段
    final字段无法在clone中赋值,限制使用场景。
  • 异常处理复杂
    需要处理CloneNotSupportedException 异常,必须try-catch。
  1. // 需要捕获异常或抛出
  2. try {
  3.     Person cloned = (Person) original.clone();
  4. } catch (CloneNotSupportedException e) {
  5.     e.printStackTrace();
  6. }
复制代码
如果一个类实现了 Cloneable 接口,那么 Object 的 clone 方法将返回该对象的逐个属性(field-by-field)拷贝,属性中包含对象(嵌套对象)也需要实现Cloneable接口,否则会抛出 CloneNotSupportedException 异常。
因此,固然可以重写clone()来支持深拷贝,但是并不发起如许做。
合适的深拷贝



  • 手动递归+clone()方法
    递归调用clone方法(),可以结合Stream API。如常见的List深拷贝,借助主流工具包Google Guava 的 Lists.newArrayList()然后clone():
  1. // 使用 Guava 和手动 clone
  2. List<Person> deepCopy = Lists.newArrayList(
  3.     originalList.stream()
  4.                 .map(p -> (Person) p.clone()) // 假设 Person 实现 Cloneable
  5.                 .toList()
  6. );
复制代码


  • 使用构造函数拷贝
    《Effective Java》保举的方式。通过构造函数创建新对象,递归复制字段。
    如许可以支持final字段,逻辑清楚,易于维护和扩展。但是需要为每个类编写拷贝逻辑。
  • 使用序列化方式
    通过序列化(如ObjectOutputStream)将对象转为字节省,再反序列化生成副本。
    这种方式性能开销大,且需要对象类实现Serializable。
    可以自己实现,也可以使用第三方的。
    如Apache Commons Lang 的 SerializationUtils
  1. import org.apache.commons.lang3.SerializationUtils;
  2. // 使用 Apache Commons 的 SerializationUtils
  3. List<Person> deepCopy = SerializationUtils.clone(originalList);
复制代码
Jackson实现了不需要实现Serializable接口的方式,使用JSON序列化,性能较低
  1. import com.fasterxml.jackson.databind.ObjectMapper;
  2. ObjectMapper objectMapper = new ObjectMapper();
  3. List<Person> deepCopy = objectMapper.readValue(
  4.     objectMapper.writeValueAsString(originalList),
  5.     new TypeReference<List<Person>>() {}
  6. );
复制代码


  • 简单场景:构造函数方式,代码简洁。
  • 高性能需求:手动实现 Cloneable 接口,控制深拷贝逻辑。
  • 复杂嵌套对象:序列化方法+第三方工具(如 SerializationUtils 或 Jackson),主动处理嵌套对象。
《Effective Java》一书中提到:
   如果你继承一个已经实现了 Cloneable 接口的类,你别无选择,只能实现一个行为良好的 clone 方法。 否则,通常你最好提供另一种对象复制方法。 对象复制更好的方法是提供一个复制构造方法或复制工厂。
  即,保举使用构造方法大概复制工厂。
  1. // Copy constructor
  2. public Yum(Yum yum){...};
  3. // Copy factory
  4. public static Yum newInstance(Yum yum){...};
复制代码
这两种方式更为灵活、不受接口实现、范例、final的限制。
另外附一段构造函数+序列化例子
  1. import java.io.*;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. // 嵌套对象类
  6. class InnerObject implements Serializable {
  7.     private int value;
  8.     public InnerObject(int value) {
  9.         this.value = value;
  10.     }
  11.     public int getValue() {
  12.         return value;
  13.     }
  14.     public void setValue(int value) {
  15.         this.value = value;
  16.     }
  17.     // 拷贝构造函数
  18.     public InnerObject(InnerObject other) {
  19.         this.value = other.value;
  20.     }
  21. }
  22. // 外层对象类
  23. class OuterObject implements Serializable {
  24.     private int number;
  25.     private InnerObject inner;
  26.     private List<Integer> numbers;
  27.     public OuterObject(int number, InnerObject inner, List<Integer> numbers) {
  28.         this.number = number;
  29.         this.inner = inner;
  30.         this.numbers = numbers;
  31.     }
  32.     public int getNumber() {
  33.         return number;
  34.     }
  35.     public void setNumber(int number) {
  36.         this.number = number;
  37.     }
  38.     public InnerObject getInner() {
  39.         return inner;
  40.     }
  41.     public List<Integer> getNumbers() {
  42.         return numbers;
  43.     }
  44.     // 拷贝构造函数(深拷贝)
  45.     public OuterObject(OuterObject other) {
  46.         this.number = other.number;
  47.         this.inner = new InnerObject(other.inner); // 深拷贝嵌套对象
  48.         this.numbers = new ArrayList<>(other.numbers); // 深拷贝集合
  49.     }
  50.     // 序列化实现深拷贝
  51.     public OuterObject deepCopyViaSerialization() {
  52.         try {
  53.             // 序列化
  54.             ByteArrayOutputStream bos = new ByteArrayOutputStream();
  55.             ObjectOutputStream oos = new ObjectOutputStream(bos);
  56.             oos.writeObject(this);
  57.             // 反序列化
  58.             ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
  59.             ObjectInputStream ois = new ObjectInputStream(bis);
  60.             return (OuterObject) ois.readObject();
  61.         } catch (IOException | ClassNotFoundException e) {
  62.             throw new RuntimeException("Deep copy failed", e);
  63.         }
  64.     }
  65. }
  66. public class DeepCopyExample {
  67.     public static void main(String[] args) {
  68.         // 创建原始对象
  69.         InnerObject inner = new InnerObject(10);
  70.         List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3));
  71.         OuterObject original = new OuterObject(100, inner, numbers);
  72.         // 拷贝构造函数深拷贝
  73.         OuterObject copyConstructor = new OuterObject(original);
  74.         // 序列化深拷贝
  75.         OuterObject copySerialized = original.deepCopyViaSerialization();
  76.         // 修改副本
  77.         copyConstructor.setNumber(888);
  78.         copyConstructor.getInner().setValue(444);
  79.         copyConstructor.getNumbers().set(0, 999);
  80.         copySerialized.setNumber(777);
  81.         copySerialized.getInner().setValue(333);
  82.         copySerialized.getNumbers().set(0, 666);
  83.         // 输出结果
  84.         System.out.println("原始对象: number=" + original.getNumber() +
  85.                 ", inner.value=" + original.getInner().getValue() +
  86.                 ", numbers=" + original.getNumbers());
  87.         System.out.println("拷贝构造: number=" + copyConstructor.getNumber() +
  88.                 ", inner.value=" + copyConstructor.getInner().getValue() +
  89.                 ", numbers=" + copyConstructor.getNumbers());
  90.         System.out.println("序列化拷贝: number=" + copySerialized.getNumber() +
  91.                 ", inner.value=" + copySerialized.getInner().getValue() +
  92.                 ", numbers=" + copySerialized.getNumbers());
  93.     }
  94. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

铁佛

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表