目录
clone()的基本概念- 如何调用
clone()?- 步骤 1:让类实现
Cloneable接口 - 步骤 2:重写
clone()方法 - 步骤 3:处理异常
- 完整代码示例
- 步骤 1:让类实现
clone()的工作原理:浅拷贝 vs. 深拷贝- 浅拷贝
- 深拷贝
- 代码示例对比
clone()的严重缺点和为什么应该避免它- 推荐的替代方案
- 拷贝构造器
- 静态工厂方法
- 序列化/反序列化
clone() 的基本概念
clone() 是 Object 类中的一个受保护的方法,它的设计初衷是创建并返回一个对象的新副本,新副本的初始状态与原始对象相同。

关键点:
- 受保护方法:你不能直接调用任何对象的
clone(),myObject.clone()是不合法的,会编译报错,只有在定义该类的包内,或者该类的子类中才能直接调用。 Cloneable接口:虽然Object类定义了clone()方法,但它并不直接实现拷贝逻辑,Java 规定,如果一个类想要支持Object.clone(),它必须实现Cloneable接口,这个接口是一个标记接口,它里面没有任何方法,实现它只是告诉 JVM:“这个类可以被安全地拷贝”。CloneNotSupportedException:如果一个类没有实现Cloneable接口,那么当调用其clone()方法时,JVM 会抛出CloneNotSupportedException异常。
如何调用 clone()?
要正确地使用 clone(),你需要遵循以下三个步骤。
步骤 1:让类实现 Cloneable 接口
这是最基本的先决条件。
public class MyClass implements Cloneable {
// ... 类内容
}
步骤 2:重写 clone() 方法
为了能从类的外部调用 clone(),你需要将其重写为 public 方法,最佳实践是让该方法返回当前类的类型,而不是通用的 Object 类型,这被称为协变返回类型。

@Override
public MyClass clone() {
// ... 拷贝逻辑
}
步骤 3:处理异常
Object.clone() 方法会抛出 CloneNotSupportedException,所以你的重写方法也需要处理这个异常(要么 throws,要么 try-catch)。
完整代码示例
下面是一个简单的 Person 类,它实现了 clone()。
import java.util.Arrays;
// 1. 实现 Cloneable 接口
class Person implements Cloneable {
private String name;
private int age;
private int[] scores; // 一个可变对象
public Person(String name, int age, int[] scores) {
this.name = name;
this.age = age;
// 重要:这里进行数组拷贝,而不是直接赋值引用
this.scores = Arrays.copyOf(scores, scores.length);
}
// 2. 重写 clone() 方法为 public
@Override
public Person clone() {
Person clonedPerson = null;
try {
// 3. 调用父类的 clone() 方法,它会进行浅拷贝
clonedPerson = (Person) super.clone();
// 如果需要对可变成员进行深拷贝,在这里手动处理
clonedPerson.scores = Arrays.copyOf(this.scores, this.scores.length);
} catch (CloneNotSupportedException e) {
// 因为 Person 实现了 Cloneable,所以这个异常理论上不会发生
throw new AssertionError(e);
}
return clonedPerson;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", scores=" + Arrays.toString(scores) +
'}';
}
}
public class Main {
public static void main(String[] args) {
int[] scores = {100, 90, 80};
Person original = new Person("Alice", 30, scores);
// 调用 clone() 方法
Person cloned = original.clone();
System.out.println("Original: " + original);
System.out.println("Cloned: " + cloned);
// 修改克隆对象中的数组
cloned.scores[0] = 50;
System.out.println("\nAfter modifying cloned's scores:");
System.out.println("Original: " + original); // 原对象的数组也被修改了!
System.out.println("Cloned: " + cloned);
}
}
注意:上面的例子中,我们手动处理了
int[]的拷贝,以确保这是一个深拷贝,关于浅拷贝和深拷贝,我们接下来会详细解释。
clone() 的工作原理:浅拷贝 vs. 深拷贝
Object.clone() 方法默认执行的是浅拷贝。

浅拷贝
- 定义:创建一个新的对象,新对象的基本类型成员变量与原始对象的值相同,对于引用类型成员变量,新对象和原始对象共享同一个引用,指向堆内存中的同一个对象。
- 后果:如果你修改了克隆对象中的某个可变引用类型成员(如
ArrayList,int[], 自定义对象),这个修改会反映到原始对象上,因为它们指向的是同一个东西。
浅拷贝示例:
class Address {
String city;
Address(String city) { this.city = city; }
}
class Employee implements Cloneable {
String name;
Address address; // 引用类型
Employee(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
public Employee clone() {
try {
return (Employee) super.clone(); // 默认浅拷贝
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}
public class ShallowCopyDemo {
public static void main(String[] args) {
Address address = new Address("New York");
Employee original = new Employee("John", address);
Employee cloned = original.clone();
System.out.println("Original Employee's City: " + original.address.city); // New York
System.out.println("Cloned Employee's City: " + cloned.address.city); // New York
// 修改克隆对象的地址
cloned.address.city = "Boston";
System.out.println("\nAfter modifying cloned's address:");
System.out.println("Original Employee's City: " + original.address.city); // Boston! 原对象也被影响了!
System.out.println("Cloned Employee's City: " + cloned.address.city); // Boston
}
}
深拷贝
- 定义:创建一个新的对象,新对象的所有成员变量(包括引用类型指向的对象)都会被递归地创建一份全新的副本,新对象和原始对象完全独立,互不影响。
- 如何实现:
- 在
clone()方法中,对于每一个可变的引用类型成员,手动创建一个副本并赋值给克隆对象。 - 让每个内部的可变类也实现
Cloneable接口,并在外层类的clone()方法中递归调用它们的clone()方法。
- 在
深拷贝示例 (基于上面的浅拷贝代码修改):
class Address implements Cloneable {
String city;
Address(String city) { this.city = city; }
@Override
public Address clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}
class Employee implements Cloneable {
String name;
Address address;
Employee(String name, Address address) {
this.name = name;
this.address = address;
}
// 实现深拷贝
@Override
public Employee clone() {
Employee cloned = null;
try {
cloned = (Employee) super.clone(); // 浅拷贝基本类型和引用
// 对引用类型成员进行深拷贝
cloned.address = this.address.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
return cloned;
}
}
public class DeepCopyDemo {
public static void main(String[] args) {
Address address = new Address("New York");
Employee original = new Employee("John", address);
Employee cloned = original.clone();
System.out.println("Original Employee's City: " + original.address.city); // New York
System.out.println("Cloned Employee's City: " + cloned.address.city); // New York
// 修改克隆对象的地址
cloned.address.city = "Boston";
System.out.println("\nAfter modifying cloned's address:");
System.out.println("Original Employee's City: " + original.address.city); // New York! 原对象不受影响
System.out.println("Cloned Employee's City: " + cloned.address.city); // Boston
}
}
clone() 的严重缺点和为什么应该避免它
尽管 clone() 存在,但 Joshua Bloch 在其经典著作《Effective Java》中明确指出:通常情况下,最好避免使用 clone() 方法。
原因如下:
- 接口与实现混淆:
clone()是一个Object类的方法,但它却与具体的类实现紧密相关,这违反了面向对象设计的“接口与实现分离”原则,调用clone()时,你实际上是在调用一个受保护的、与具体实现细节相关的方法。 - 浅拷贝的陷阱:如前所述,
Object.clone()默认是浅拷贝,开发者很容易忘记处理引用类型成员,从而在无意中导致程序 bug(一个对象的修改影响另一个)。 - 构造函数被绕过:
clone()的过程是:创建一个新对象,然后逐个字段地复制原始对象的值。它不会调用被克隆对象的构造函数,这会带来一些问题:- 如果构造函数中做了一些不可重入的初始化逻辑(比如启动一个线程、建立网络连接),这些逻辑在
clone()时会被跳过,可能导致克隆对象处于不一致的状态。 - 对于“final”字段,如果它们不是基本类型或不可变对象,
clone()可能无法正确复制它们(尽管 Java 允许在clone()方法中修改 final 字段,但这很危险且不常见)。
- 如果构造函数中做了一些不可重入的初始化逻辑(比如启动一个线程、建立网络连接),这些逻辑在
- 方法签名不清晰:
clone()返回Object类型,强制要求每次调用后都要进行强制类型转换,这很繁琐且容易出错。 - 文档约定不明确:
Cloneable接口没有指定clone()方法的行为,它只表示“可以被克隆”,但如何克隆、拷贝的深度(深/浅)完全由类的实现者决定,这增加了使用者的理解成本。
推荐的替代方案
既然 clone() 有这么多问题,我们应该用什么来替代呢?以下是几种更安全、更清晰的方式。
拷贝构造器
提供一个构造函数,其参数是同类型的另一个对象,这个构造函数负责创建新对象并复制所有成员。
class Person {
private String name;
// ... 其他字段
// 原始构造函数
public Person(String name) { ... }
// 拷贝构造器
public Person(Person other) {
this.name = other.name; // 基本类型直接复制
// 如果有引用类型,需要在这里手动进行深拷贝
// this.address = new Address(other.address);
}
}
// 使用
Person original = new Person("Alice");
Person copied = new Person(original); // 非常清晰明了
优点:
- 非常直观,符合 Java 构造对象的习惯。
- 不需要实现任何特殊接口或重写方法。
- 没有隐藏的规则,代码可读性高。
静态工厂方法
创建一个静态方法,接收一个对象作为参数,并返回该对象的一个副本。
class Person {
// ... 同上
// 静态工厂方法
public static Person copyOf(Person other) {
Person p = new Person();
p.name = other.name;
// ... 深拷贝逻辑
return p;
}
}
// 使用
Person original = new Person("Alice");
Person copied = Person.copyOf(original);
或者更简洁的写法(利用 Java 16 的 record 特性,record 会自动生成 equals, hashCode, toString,并且推荐使用拷贝构造器):
record Point(int x, int y) {}
// Point p1 = new Point(1, 2);
// Point p2 = new Point(p1); // 编译器自动生成的拷贝构造器
优点:
- 比拷贝构造器更灵活,可以给方法起一个更清晰的名字(如
copyOf,newInstanceFrom)。 - 可以返回原类型的子类,这是构造器做不到的。
序列化/反序列化
将对象序列化为字节流,然后再从字节流反序列化出一个新的对象,这种方式天然就是深拷贝。
import java.io.*;
public class DeepCopyUtils {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepCopy(T object) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (T) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("Failed to deep copy object", e);
}
}
}
// 使用
Person original = new Person("Alice");
Person copied = DeepCopyUtils.deepCopy(original);
优点:
- 实现简单,可以处理非常复杂的对象图,无需手动编写深拷贝逻辑。
- 保证是真正的深拷贝。
缺点:
- 性能开销大:涉及 I/O 操作,比拷贝构造器慢得多。
- 类必须实现
Serializable接口。 - 不安全:会拷贝所有字段,包括那些你不希望被拷贝的(如缓存、数据库连接句柄等),如果对象图中有不可序列化的对象,会直接抛出异常。
| 特性 | clone() |
拷贝构造器 | 静态工厂方法 | 序列化/反序列化 |
|---|---|---|---|---|
| 简洁性 | 差(需实现接口、重写、处理异常) | 优 | 优 | 中 |
| 安全性 | 差(浅拷贝陷阱、绕过构造函数) | 优(显式、可控) | 优(显式、可控) | 中(可能拷贝不该拷贝的字段) |
| 性能 | 中 | 高 | 高 | 低 |
| 可读性 | 差(与实现耦合) | 优 | 优 | 中 |
| 适用场景 | 几乎不推荐 | 首选方案 | 灵活场景,如返回子类 | 对象图复杂,且类都 Serializable |
最终建议:
在 99% 的情况下,请避免使用 clone(),优先选择拷贝构造器或静态工厂方法来创建对象的副本,它们更安全、更清晰,也更符合 Java 的设计哲学,只有在需要处理极其复杂的对象图,并且性能不是关键瓶颈时,才考虑使用序列化/反序列化的方式。
