深拷贝 vs 浅拷贝

一、结论先看(一句话)

  • 浅拷贝 :只复制外层对象 ;其中的引用字段仅复制"指针" ,仍指向同一 子对象。→ 修改可变的子对象,会影响彼此。
  • 深拷贝 :复制对象图中需要的层级 ;引用字段也会新建独立副本 。→ 修改任意一边互不影响

二、快速对比(要点表)

维度 浅拷贝 (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),浅拷也安全。

  • 对外暴露只读视图或防御性拷贝:

    csharp 复制代码
    java
    复制编辑
    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 只是标记。
  • 深拷需:类实现 Cloneableclone() 里对所有可变引用字段 继续 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 ;能不用深拷就用不可变 + 防御性拷贝

相关推荐
叽哥14 分钟前
flutter学习第 14 节:动画与过渡效果
android·flutter·ios
小仙女喂得猪22 分钟前
2025再读Android RecyclerView源码
android·android studio
BoomHe24 分钟前
车载 XCU 的简单介绍
android
锅拌饭1 小时前
RecyclerView 缓存复用导致动画失效问题
android
程序员老刘1 小时前
操作系统“卡脖子”到底是个啥?
android·开源·操作系统
拭心1 小时前
一键生成 Android 适配不同分辨率尺寸的图片
android·开发语言·javascript
2501_915918412 小时前
iOS 文件管理全流程实战,从开发调试到数据迁移
android·ios·小程序·https·uni-app·iphone·webview
一枚小小程序员哈3 小时前
基于Android的音乐播放器/基于android studio的音乐系统/音乐管理系统
android·ide·android studio
叽哥3 小时前
flutter学习第 13 节:本地存储
android·flutter·ios