[Java实战经验]对象拷贝

首先,对于不可变的类,我们不应该实现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。

// 需要捕获异常或抛出
try {
    Person cloned = (Person) original.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

如果一个类实现了 Cloneable 接口,那么 Object 的 clone 方法将返回该对象的逐个属性(field-by-field)拷贝,属性中包含对象(嵌套对象)也需要实现Cloneable接口,否则会抛出 CloneNotSupportedException 异常。

因此,虽然可以重写clone()来支持深拷贝,但是并不建议这样做。

合适的深拷贝

  • 手动递归+clone()方法
    递归调用clone方法(),可以结合Stream API。如常见的List深拷贝,借助主流工具包Google Guava 的 Lists.newArrayList()然后clone():
// 使用 Guava 和手动 clone
List<Person> deepCopy = Lists.newArrayList(
    originalList.stream()
                .map(p -> (Person) p.clone()) // 假设 Person 实现 Cloneable
                .toList()
);
  • 使用构造函数拷贝
    《Effective Java》推荐的方式。通过构造函数创建新对象,递归复制字段。
    这样可以支持final字段,逻辑清晰,易于维护和扩展。但是需要为每个类编写拷贝逻辑。

  • 使用序列化方式
    通过序列化(如ObjectOutputStream)将对象转为字节流,再反序列化生成副本。
    这种方式性能开销大,且需要对象类实现Serializable。
    可以自己实现,也可以使用第三方的。
    如Apache Commons Lang 的 SerializationUtils

import org.apache.commons.lang3.SerializationUtils;

// 使用 Apache Commons 的 SerializationUtils
List<Person> deepCopy = SerializationUtils.clone(originalList);

Jackson实现了不需要实现Serializable接口的方式,使用JSON序列化,性能较低

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper objectMapper = new ObjectMapper();
List<Person> deepCopy = objectMapper.readValue(
    objectMapper.writeValueAsString(originalList),
    new TypeReference<List<Person>>() {}
);
  • 简单场景:构造函数方式,代码简洁。
  • 高性能需求:手动实现 Cloneable 接口,控制深拷贝逻辑。
  • 复杂嵌套对象:序列化方法+第三方工具(如 SerializationUtilsJackson),自动处理嵌套对象。

《Effective Java》一书中提到:

如果你继承一个已经实现了 Cloneable 接口的类,你别无选择,只能实现一个行为良好的 clone 方法。 否则,通常你最好提供另一种对象复制方法。 对象复制更好的方法是提供一个复制构造方法或复制工厂。

即,推荐使用构造方法或者复制工厂。

// Copy constructor
public Yum(Yum yum){...};

// Copy factory
public static Yum newInstance(Yum yum){...};

这两种方式更为灵活、不受接口实现、类型、final的限制。

另外附一段构造函数+序列化例子


import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

// 嵌套对象类
class InnerObject implements Serializable {
    private int value;

    public InnerObject(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    // 拷贝构造函数
    public InnerObject(InnerObject other) {
        this.value = other.value;
    }
}

// 外层对象类
class OuterObject implements Serializable {
    private int number;
    private InnerObject inner;
    private List<Integer> numbers;

    public OuterObject(int number, InnerObject inner, List<Integer> numbers) {
        this.number = number;
        this.inner = inner;
        this.numbers = numbers;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public InnerObject getInner() {
        return inner;
    }

    public List<Integer> getNumbers() {
        return numbers;
    }

    // 拷贝构造函数(深拷贝)
    public OuterObject(OuterObject other) {
        this.number = other.number;
        this.inner = new InnerObject(other.inner); // 深拷贝嵌套对象
        this.numbers = new ArrayList<>(other.numbers); // 深拷贝集合
    }

    // 序列化实现深拷贝
    public OuterObject deepCopyViaSerialization() {
        try {
            // 序列化
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 反序列化
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (OuterObject) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Deep copy failed", e);
        }
    }
}

public class DeepCopyExample {
    public static void main(String[] args) {
        // 创建原始对象
        InnerObject inner = new InnerObject(10);
        List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3));
        OuterObject original = new OuterObject(100, inner, numbers);

        // 拷贝构造函数深拷贝
        OuterObject copyConstructor = new OuterObject(original);

        // 序列化深拷贝
        OuterObject copySerialized = original.deepCopyViaSerialization();

        // 修改副本
        copyConstructor.setNumber(888);
        copyConstructor.getInner().setValue(444);
        copyConstructor.getNumbers().set(0, 999);

        copySerialized.setNumber(777);
        copySerialized.getInner().setValue(333);
        copySerialized.getNumbers().set(0, 666);

        // 输出结果
        System.out.println("原始对象: number=" + original.getNumber() +
                ", inner.value=" + original.getInner().getValue() +
                ", numbers=" + original.getNumbers());
        System.out.println("拷贝构造: number=" + copyConstructor.getNumber() +
                ", inner.value=" + copyConstructor.getInner().getValue() +
                ", numbers=" + copyConstructor.getNumbers());
        System.out.println("序列化拷贝: number=" + copySerialized.getNumber() +
                ", inner.value=" + copySerialized.getInner().getValue() +
                ", numbers=" + copySerialized.getNumbers());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值