一、结论先看(一句话)
- 浅拷贝 :只复制外层对象 ;其中的引用字段仅复制"指针" ,仍指向同一 子对象。→ 修改可变的子对象,会影响彼此。
- 深拷贝 :复制对象图中需要的层级 ;引用字段也会新建独立副本 。→ 修改任意一边互不影响。
二、快速对比(要点表)
维度 | 浅拷贝 (shallow) | 深拷贝 (deep) |
---|---|---|
复制范围 | 外层对象;引用字段只是复制地址 | 外层 + 需要层级的子对象都新建 |
可变子对象 | 共享 | 独立 |
速度/内存 | 快、占用少 | 慢、占用多 |
复杂度 | O(字段数) | O(对象图大小),需处理环/共享 |
典型 API | Java Object.clone() 默认浅;Kotlin data class copy() 浅;new ArrayList<>(list) 浅 |
手写拷贝构造/工厂;逐层 clone() ;序列化反序列化;图遍历保留别名关系 |
适用 | 内部字段不可变/只读;共享可接受 | 需要强隔离、防御性拷贝、并发安全边界 |
三、代码直观感受
1) Java:人-地址示例
typescript
java
复制编辑
class Address implements Cloneable {
String city;
Address(String c){ city=c; }
@Override public Address clone() {
try { return (Address) super.clone(); } catch (Exception e) { throw new AssertionError(e); }
}
}
class Person implements Cloneable {
String name; Address addr;
Person(String n, Address a){ name=n; addr=a; }
// 浅拷贝:addr 还是同一对象
@Override public Person clone() {
try { return (Person) super.clone(); } catch (Exception e) { throw new AssertionError(e); }
}
// 深拷贝:把 addr 也复制
public Person deepCopy() {
Person p = this.clone();
p.addr = this.addr.clone();
return p;
}
}
public class Demo {
public static void main(String[] args) {
Person p1 = new Person("Alice", new Address("Tokyo"));
Person s = p1.clone();
Person d = p1.deepCopy();
p1.addr.city = "Osaka";
System.out.println(s.addr.city); // Osaka(浅拷贝受影响)
System.out.println(d.addr.city); // Tokyo(深拷贝不受影响)
}
}
2) Kotlin:data class copy()
是浅拷贝
kotlin
kotlin
复制编辑
data class Address(var city: String)
data class Person(var name: String, var addr: Address)
val p1 = Person("Alice", Address("Tokyo"))
val shallow = p1.copy() // 浅拷贝
val deep = p1.copy(addr = p1.addr.copy()) // 手动深拷贝嵌套
p1.addr.city = "Osaka"
println(shallow.addr.city) // Osaka
println(deep.addr.city) // Tokyo
四、集合与数组:常见误区&正确姿势
1) ArrayList/Map/Set 的"复制构造"大多是浅拷贝容器
ini
java
复制编辑
List<Person> src = List.of(new Person("A", new Address("Tokyo")));
List<Person> shallowList = new ArrayList<>(src); // 只复制"壳",元素引用共享
// 深拷贝要"连元素一起拷":
List<Person> deepList = new ArrayList<>(src.size());
for (Person p : src) deepList.add(p.deepCopy());
2) 数组
ini
java
复制编辑
int[] a = {1,2,3}; int[] b = a.clone(); // 基本类型数组 -> 值复制(相当于深)
// 对象数组 -> 浅:仅复制引用
Address[] x = { new Address("Tokyo") };
Address[] y = x.clone();
x[0].city = "Osaka";
System.out.println(y[0].city); // Osaka
3) ArrayList.clone()
的语义(容易被问)
- 会复制底层 elementData 数组 (结构独立),但不复制元素对象(元素共享)。
- 因此:对列表的结构修改互不影响;对元素内部状态修改彼此影响(若元素可变)。
五、工程实现套路(按"强度"递增)
1) 拷贝构造/工厂(推荐)
最清晰可控,类型安全,能明确哪些字段深拷、哪些直接复用。
kotlin
java
复制编辑
class Order {
final String id;
final List<Item> items;
Order(Order o){ // defensive deep copy
this.id = o.id;
this.items = new ArrayList<>(o.items.size());
for (Item it : o.items) this.items.add(it.deepCopy());
}
}
2) 逐层 clone
对象较多时注意每层可变字段都要 clone,且留意 Cloneable
的坑(见下)。
3) 序列化反序列化(方便但有代价)
Java 原生序列化 / Kryo / JSON(Gson/Jackson)都可做深拷贝:简便、可处理复杂图,但慢 、需要可序列化 、有安全风险(反序列化漏洞)。
ini
java
复制编辑
var gson = new com.google.gson.Gson();
Person deep = gson.fromJson(gson.toJson(p1), Person.class);
4) 图结构深拷贝(保留"共享/环")
要保持同一子对象被多处引用的"别名关系",需要DFS/BFS + IdentityHashMap 记 visited:
ini
java
复制编辑
Node deepCopy(Node root, Map<Node,Node> vis) {
if (root == null) return null;
if (vis.containsKey(root)) return vis.get(root);
Node nr = new Node(root.val);
vis.put(root, nr);
for (Node nb : root.neighbors) nr.neighbors.add(deepCopy(nb, vis));
return nr;
}
5) 不可变 & 防御性拷贝(替代思路)
-
把状态设计为不可变 (
String
,LocalDateTime
, 自定义 Immutable),浅拷也安全。 -
对外暴露只读视图或防御性拷贝:
csharpjava 复制编辑 public List<Item> getItems() { return Collections.unmodifiableList(items); } public Order(List<Item> items){ this.items = List.copyOf(items); } // JDK 10+
六、性能与语义边界(容易被追问)
- 复杂度:浅拷 O(字段数);深拷 O(对象图大小)。
- 内存:深拷至少 2×;如果保留共享关系会略省,但仍大。
- 并发 :浅拷共享可变对象在并发下风险大;深拷/不可变能做"线程间边界"。
- 反序列化深拷 :注意 polymorphic 类型、
transient
字段、循环引用、精度丢失、构造器副作用。
七、面试高频题 + 详细答案(可背)
1)什么是浅拷贝、深拷贝?各自影响?
- 浅:只拷外层、引用共享;改可变 子对象会互相影响。
- 深:连子对象也拷;两边完全独立。
- 举例:
new ArrayList<>(old)
、data class copy()
是浅(元素共享);需要"连元素一起拷"。
2)Java 的 clone()
默认深还是浅?如何写深拷?
Object.clone()
默认浅 ;Cloneable
只是标记。- 深拷需:类实现
Cloneable
,clone()
里对所有可变引用字段 继续clone/深拷
。 - 但
Cloneable
设计口碑一般,推荐拷贝构造/工厂。
3)ArrayList.clone()
与 new ArrayList<>(list)
有何区别?
- 二者都让容器结构独立,元素共享 。
clone()
会把modCount
归零并遵守Cloneable
协议,其他语义近似。
4)数组的 clone()
是浅还是深?
- 基本类型数组:值复制(相当于深)。
- 对象数组:浅,复制的是引用。
5)如何深拷贝集合?
- 两步:先新建容器(复制"壳"),再遍历元素逐个深拷(元素也要支持深拷)。
- 如:
newList = old.stream().map(Item::deepCopy).collect(toList());
6)序列化/反序列化能做深拷贝吗?优缺点?
- 能;优点:实现快、适合复杂对象图。
- 缺点:性能差 、需可序列化、存在安全风险、多态/瞬态字段/精度问题要处理。生产慎用高频深拷。
7)如何深拷一个可能有环和共享边的图?
- DFS/BFS + visited(IdentityHashMap) ,遇到已复制节点直接复用副本,既防止死循环,也保留别名关系。
8)在什么情况下不需要深拷贝?
- 内部字段都是不可变对象;或对外只读曝光;或确实需要共享(读场景/缓存)。
9)深拷贝与 copy-on-write(COW)的区别?
- 深拷:拷时就复制全部(写时零成本、拷时成本高)。
- COW:写入时 才复制(读快、写贵),如
CopyOnWriteArrayList
;语义不同于"值拷贝"。
10)为什么很多项目更偏向拷贝构造器而非 clone()
?
clone()
语义怪:必须Cloneable
,默认浅,容易漏拷字段,受继承影响;- 拷贝构造器/静态工厂更显式、可测试、可限制深度、能做校验和不可变包装。
11)Kotlin data class copy()
是深拷贝吗?如何做深拷?
copy()
是浅拷 ;对含可变嵌套的字段要手动copy()
或用自定义工厂返回深拷贝版本。
12)怎么做"防御性拷贝"?为什么重要?
- 在构造器/Setter/Getter 对外部传入/暴露的可变对象做拷贝或只读包装,避免外部改变内部状态。
- 这是模块边界、并发安全、调用者隔离的关键实践。
13)深拷贝的时间/空间复杂度?
- 时间 ~ O(对象图总节点+边数);空间至少复制出同规模对象(O(N)),还可能需要 visited 哈希(O(N))。
14)如何判断你写的深拷是否正确?
- 单元测试:修改任意一边的任何可变字段都不影响另一边;
- 复杂结构:对"别名节点"验证引用相等/不等是否符合预期(有时要保留共享关系)。
15)与不可变模式的取舍?
- 不可变对象让浅拷也安全,但构建成本与对象数量可能上升;
- 热点路径下,权衡"复制成本 vs 简化并发与推理"。
八、最后的速记卡
浅 :拷外层、内层共享;深 :拷到底、互不影响。
集合深拷要"连元素一起拷 ";数组:基本类型深、对象浅。
Java
clone()
默认浅;更推荐拷贝构造/工厂 。复杂对象图:DFS/BFS + visited ;能不用深拷就用不可变 + 防御性拷贝。