Clone 方法
概述
对象复制在 Java 中不是 obj.copy() 这么简单。Object 的默认 clone() 是浅克隆——如果你有一个包含嵌套引用的对象,克隆后的对象和原对象会共享内部引用。这是一个经常被忽视的坑。
基本概念
Cloneable 接口
Cloneable 是一个标记接口,告诉 JVM 这个类可以被克隆:
java
class MyClass implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}为什么要重写?
Object.clone() 是 native 方法,直接调用会按位复制所有字段。对于基本类型没问题,但引用类型会共享地址。
代码示例
基本克隆
java
public class CloneBasicDemo {
static class Person implements Cloneable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
public String getName() { return name; }
public int getAge() { return age; }
}
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person("张三", 25);
Person p2 = p1.clone();
System.out.println("p1: " + p1.getName());
System.out.println("p2: " + p2.getName());
System.out.println("p1 == p2: " + (p1 == p2)); // false:不同对象
System.out.println("p1.name == p2.name: " + (p1.getName() == p2.getName())); // true:共享字符串
}
}浅克隆问题
java
public class ShallowCloneProblemDemo {
static class Address {
String city;
Address(String city) {
this.city = city;
}
}
static class Person implements Cloneable {
String name;
Address address; // 引用类型
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone(); // 默认浅克隆
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Address addr = new Address("北京");
Person p1 = new Person("张三", addr);
Person p2 = p1.clone();
// 两个对象共享同一个 Address
System.out.println("修改前 p1.city: " + p1.address.city); // 北京
System.out.println("修改前 p2.city: " + p2.address.city); // 北京
// 修改 p2 的地址,p1 也受影响!
p2.address.city = "上海";
System.out.println("修改后 p1.city: " + p1.address.city); // 上海 ❌
System.out.println("修改后 p2.city: " + p2.address.city); // 上海
}
}深克隆
对于有引用类型字段的对象,需要手动克隆每个引用:
java
public class DeepCloneDemo {
static class Address implements Cloneable {
String city;
Address(String city) {
this.city = city;
}
@Override
protected Address clone() throws CloneNotSupportedException {
return new Address(this.city); // 新建对象
}
}
static class Person implements Cloneable {
String name;
Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Person clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
p.address = this.address.clone(); // 深克隆:克隆嵌套对象
return p;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Address addr = new Address("北京");
Person p1 = new Person("张三", addr);
Person p2 = p1.clone();
// 修改 p2 的地址,p1 不受影响
p2.address.city = "上海";
System.out.println("p1.city: " + p1.address.city); // 北京 ✅
System.out.println("p2.city: " + p2.address.city); // 上海
}
}深克隆 vs 浅克隆
| 类型 | 描述 | 引用类型字段 |
|---|---|---|
| 浅克隆 | 只复制对象本身 | 引用地址共享 |
| 深克隆 | 递归复制所有嵌套对象 | 完全独立 |
序列化实现深克隆
对于复杂对象图,可以用序列化实现深克隆:
java
public class SerializationCloneDemo {
static class DeepCloneUtil {
public static <T extends Serializable> T deepClone(T obj) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (T) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}注意事项
- 实现 Cloneable:不实现这个接口,
clone()会抛CloneNotSupportedException - 重写
clone():调用super.clone()享受 JVM 的 native 优化 - 深克隆:对于包含引用类型字段的类,需要手动递归克隆
- 替代方案:如果不需要高性能,可以用序列化实现深克隆
- 不可变字段:
final引用且不可变对象(如String)不需要深克隆
