clone()方法的本质和来源List接口与AbstractList的实现ArrayList的clone()具体行为LinkedList的clone()具体行为- 为什么通常不推荐使用
clone()? - 推荐的替代方案(深拷贝和浅拷贝)
clone() 方法的本质和来源
在 Java 中,clone() 方法不是由 List 接口定义的,而是所有 Java 对象的“祖先”——java.lang.Object 类中的一个方法。

// 位于 Object 类中 protected native Object clone() throws CloneNotSupportedException;
关键点:
native:表示这个方法不是用 Java 实现的,而是由 JVM 直接在底层 C++ 代码中实现的。protected:这意味着只有同一个包内的类或者其子类才能直接调用它,如果你有一个List引用,你是不能直接调用list.clone()的,因为List接口没有暴露这个方法。Object.clone()的默认行为是“浅拷贝”(Shallow Copy):它会创建一个新对象,然后将原始对象中所有“字段”的值复制到新对象中,对于基本数据类型(int,double等),这没问题,但对于对象引用,它只是复制了引用地址,而不是引用所指向的对象本身,这会导致新旧对象共享同一个内部对象,从而可能引发意外问题。
List 接口与 AbstractList 的实现
由于 Object.clone() 是 protected 的,List 接口本身当然不会有 clone() 方法,那么为什么我们有时又能看到 list.clone() 呢?
这是因为 List 的主要实现类,如 ArrayList 和 LinkedList,都继承自 AbstractList,而 AbstractList 类重写了 clone() 方法并将其设为 public。
// 位于 AbstractList 类中
public Object clone() {
try {
// 调用 Object 的 native clone 方法
@SuppressWarnings("unchecked")
AbstractList<?> v = (AbstractList<?>) super.clone();
// ... 其他处理 ...
return v;
} catch (CloneNotSupportedException e) {
// 这实际上永远不会发生,因为 AbstractList 实现了 Cloneable
throw new InternalError(e);
}
}
为了使 super.clone() 能够成功,类必须实现 java.lang.Cloneable 接口,这个接口是一个“标记接口”(Marker Interface),它本身不包含任何方法,只是告诉 JVM:“这个对象可以被克隆”。

ArrayList 和 LinkedList 都实现了 Cloneable 接口,所以它们的 clone() 方法可以被调用。
ArrayList 的 clone() 具体行为(浅拷贝)
ArrayList 内部使用一个数组(elementData)来存储元素,它的 clone() 方法执行了以下操作:
- 调用
super.clone()创建一个新的ArrayList实例,这个新实例的size和原始对象一样。 - 关键一步:它将原始
ArrayList内部数组elementData的引用直接复制给了新对象的elementData。
这意味着什么?
新创建的 ArrayList 对象本身是一个新的实例,但它内部的元素数组与原始 ArrayList 共享。
让我们用代码和图示来说明:
import java.util.ArrayList;
import java.util.List;
public class ArrayListCloneExample {
public static void main(String[] args) {
// 1. 创建原始列表并添加元素
List<String> originalList = new ArrayList<>();
originalList.add("Apple");
originalList.add("Banana");
// 2. 克隆列表
// 注意:这里需要将 List 强制转换为 ArrayList 才能调用 clone()
List<String> clonedList = (ArrayList<String>) ((ArrayList<String>) originalList).clone();
// 3. 修改原始列表
originalList.set(0, "Orange"); // 修改第一个元素
// 4. 修改克隆列表的内部对象(如果元素是可变的)
// 假设我们有一个可变的元素
MutableElement element = new MutableElement("Initial");
originalList.add(element);
// 通过原始列表修改这个可变对象
element.setValue("Modified via Original");
// 5. 观察结果
System.out.println("Original List: " + originalList); // [Orange, Banana, MutableElement{value='Modified via Original'}]
System.out.println("Cloned List: " + clonedList); // [Apple, Banana, MutableElement{value='Modified via Original'}]
// 结论1:修改原始列表中的 String(不可变对象)不影响克隆列表
// 结论2:修改原始列表和克隆列表共同引用的可变对象,会影响彼此
}
static class MutableElement {
String value;
public MutableElement(String value) { this.value = value; }
public void setValue(String value) { this.value = value; }
@Override public String toString() { return "MutableElement{value='" + value + "'}"; }
}
}
内存示意图
从图中可以看出:
originalList和clonedList是两个不同的ArrayList对象。- 它们内部的
elementData数组指向的是同一个内存地址。 - 对
originalList的结构性修改(如add,remove)不会影响clonedList的size,但如果修改了数组中某个位置的元素(特别是可变对象),这个变化会同时反映在两个列表中。
LinkedList 的 clone() 具体行为
LinkedList 的 clone() 行为与 ArrayList 类似,也是浅拷贝。
LinkedList 内部使用 Node 对象(包含 item, next, prev)来构成链表。clone() 方法会遍历原始链表,为每个元素创建一个新的 Node 对象,但新 Node 中的 item 字段(即列表中的元素)是直接从原始 Node 复制过来的引用。
LinkedList 的克隆结果也是一个新的 LinkedList 实例,但其节点中的元素与原始列表的节点共享引用。
为什么通常不推荐使用 clone()?
- 混淆的语义:
clone()的行为是“浅拷贝”,但这与很多开发者直觉上的“克隆”(即完全独立的副本)相悖,人们期望的是深拷贝。 - 受保护的设计:
Object.clone()是protected的,这本身就是一个设计信号,表明它不应该被随意作为公共 API 使用。 - Cloneable 接口的设计缺陷: Joshua Bloch 在《Effective Java》中称
Cloneable接口的设计是“一个巨大的错误”,它没有定义clone()方法,却改变了Object.clone()的行为(从抛出异常到成功),这种基于接口的异常机制非常反直觉。 - 性能和不确定性:克隆过程的具体实现依赖于各个类,可能比其他创建副本的方式更慢或更不可预测。
推荐的替代方案
既然 clone() 不推荐,那么我们应该如何创建 List 的副本呢?根据需求选择“浅拷贝”或“深拷贝”。
构造函数(推荐用于浅拷贝)
这是最简单、最清晰、最受推荐的创建浅拷贝的方法。
List<String> originalList = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 创建一个与 originalList 内容相同的新列表
List<String> shallowCopy = new ArrayList<>(originalList);
// 或者
List<String> shallowCopy2 = originalList.stream().collect(Collectors.toList());
优点:
- 代码清晰易懂,意图明确。
- 不依赖于任何可能被废弃或行为怪异的
clone()机制。 - 适用于所有标准的
Collection实现类。
手动实现深拷贝
当你需要创建一个深拷贝(即列表中的所有元素也都是独立的副本)时,必须手动实现。
假设你有一个 Person 类:
class Person {
private String name;
private int age;
// constructor, getters, setters
}
深拷贝示例:
List<Person> originalList = new ArrayList<>();
originalList.add(new Person("Alice", 30));
originalList.add(new Person("Bob", 25));
// 手动创建深拷贝
List<Person> deepCopy = new ArrayList<>(originalList.size());
for (Person person : originalList) {
// 为每个元素创建一个新实例并复制数据
// 假设 Person 有一个拷贝构造函数
deepCopy.add(new Person(person.getName(), person.getAge()));
// 或者使用 setter
// Person newPerson = new Person();
// newPerson.setName(person.getName());
// newPerson.setAge(person.getAge());
// deepCopy.add(newPerson);
}
// 修改 originalList 中的任何 Person 对象都不会影响 deepCopy
originalList.get(0).setName("Alice Smith");
System.out.println(deepCopy.get(0).getName()); // 输出仍然是 "Alice"
如果列表中的元素本身也包含可变对象,那么你需要递归地进行拷贝,这会变得非常复杂,这时可以考虑使用序列化/反序列化的方式来实现通用的深拷贝工具类(但要注意性能和对象必须实现 Serializable 接口的问题)。
| 方法 | 类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
list.clone() |
浅拷贝 | API 直接 | 语义模糊、设计有缺陷、不推荐 | 几乎不推荐使用,除非你非常清楚其行为且维护旧代码。 |
new ArrayList<>(list) |
浅拷贝 | 代码清晰、意图明确、推荐 | 只能实现浅拷贝 | 创建 List 浅拷贝的首选方法。 |
| 手动循环拷贝 | 深拷贝 | 完全控制拷贝逻辑 | 代码冗长,需要为每个类实现拷贝逻辑 | 当你需要深拷贝且元素类型可控时。 |
核心建议:忘记 List 的 clone() 方法吧,在 99% 的情况下,使用构造函数 new ArrayList<>(originalList) 来创建一个浅拷贝的列表副本,这是最安全、最清晰、最符合 Java 编程习惯的做法,如果需要深拷贝,则手动实现拷贝逻辑。
