浅拷贝只复制引用地址而非对象本身,导致原数组与副本共享同一堆内存中的对象,修改对象状态会双向可见;深拷贝虽可隔离但存在性能、类型和循环引用等限制。

数组拷贝中引用数据类型的副作用,核心在于“复制了地址,没复制对象”。只要副本和原数组指向同一个堆内存中的对象,后续对这个对象状态的任何修改,就会双向可见——这不是 bug,而是引用语义的必然结果。
浅拷贝只搬引用,不搬对象
Java 中的 clone()、Arrays.copyOf() 或 JS 中的 [...arr]、slice(),对引用类型元素都只复制其内存地址。比如 Person[] 数组拷贝后,新旧数组的每个索引位置仍指向同一个 Person 实例。
- 修改副本中某个人的 age 字段 → 原数组对应 Person 的 age 同步变
- 向副本中某个 ArrayList 元素 add() 新项 → 原数组同位置的列表也多了一项
- String[] 是个例外:因 String 不可变,共享引用不会导致内容被改,但仍是浅拷贝
多维数组的陷阱更隐蔽
像 Person[][] 或 ArrayList<string>[]</string> 这类结构,外层数组和内层数组都是对象。用 System.arraycopy 或 clone() 只处理外层,内层数组引用依然共享。
-
grid[0][0] = 99→ 若grid[0]和副本的copy[0]指向同一 int[],原数组也被改 - 真正隔离需两层操作:先复制外层数组,再对每行新建子数组并逐个赋值
- String[][] 看似安全,是因为 String 不可变;换成 StringBuilder[][] 就立刻暴露问题
方法传参与防御性拷贝
当数组作为参数传入方法时,Java 传递的是引用地址副本。这和浅拷贝效果一致:改元素值会影响原数组,重赋值则不会。
- 方法内写
arr[0] = 100→ 原数组首元素变成 100 - 方法内写
arr = new int[]{1,2,3}→ 仅局部变量换指向,原数组不动 - 若方法逻辑可能修改数组内容,应在入口处主动做防御性拷贝:
Arrays.copyOf(arr, arr.length)
深拷贝不是万能解药
深拷贝能切断引用链,但代价明确:性能开销、类型限制、循环引用风险。
- 手动遍历 + clone() 要求每个对象正确实现
Cloneable,否则抛异常 - 序列化方案(如
SerializationUtils.clone())要求类实现Serializable,且无法处理 transient 字段或非 serializable 成员 - JSON 序列化会丢失函数、Date 对象行为、undefined、原型链等,属于“失真深拷贝”
- 递归实现若无缓存或深度控制,遇到环状结构易栈溢出


















