首先,对于不可变的类,我们不应该实现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
接口,控制深拷贝逻辑。 - 复杂嵌套对象:序列化方法+第三方工具(如
SerializationUtils
或Jackson
),自动处理嵌套对象。
《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());
}
}