Java数组拷贝中,浅拷贝并非低效的代名词——恰恰相反,它是多数场景下最高效、最安全的选择,前提是理解其行为边界。关键不在于“是否要深拷贝”,而在于“何时只需浅拷贝”。

先明确一个问题:浅拷贝究竟复制了什么?是数组容器本身,还是容器内的元素?答案很简单——浅拷贝仅创建新数组容器,而不会重新构造元素。对于基本类型(如int、boolean),值本身被真实复制;对于引用类型(如String、自定义对象、List),复制的是内存地址,因此新旧数组指向同一堆对象。请看示例:
int[] arr1 = {1, 2, 3}; int[] arr2 = arr1.clone();→ 修改arr2[0]不会影响arr1→ 因为int是值类型Person[] p1 = {new Person("A")}; Person[] p2 = p1.clone();→p1[0] == p2[0]结果为true → 因为Person是引用,地址被复制- 二维数组
int[][] mat = {{1,2}}; int[][] copy = mat.clone();→copy[0] == mat[0]→ 外层数组新,内层数组仍共享
理解了行为边界后,接下来看四种主流的浅拷贝实现方式。写法不同,性能与可读性略有差异,但本质一致:不穿透引用层级。
- Arrays.copyOf(arr, len):推荐首选。语义清晰,内部调用System.arraycopy,JVM优化充分,支持泛型推导(如String[])
- arr.clone():轻量直接,无需import,但返回Object类型需强转(如 (String[]) arr.clone())
- System.arraycopy(src, 0, dst, 0, len):底层控制力最强,适合复用已有目标数组、避免新建对象,或在循环中批量拷贝
- for循环赋值:显式可控,便于插入逻辑(如过滤、转换),但代码略冗长,JVM无法做批量内存优化
这里必须提醒一句:很多开发者以为某些写法实现了深度隔离,结果改了一个子对象,整个数组都跟着变。下面这几个“看似深”的操作,本质上仍是浅拷贝。
Arrays.copyOf()用于对象数组 → 元素引用未变,Person[]拷贝后改name,原数组同步变ArrayList构造函数new ArrayList(list)→ 底层仍是浅拷贝,list中的Person对象仍共用- 多维数组
clone()或Arrays.copyOf()→ 只复制第一层引用,int[][]的每一行仍是同一数组实例 String类型看似安全 → 实际因不可变性“表现像深拷贝”,但本质仍是引用复制,只是无法被修改
那么问题来了:既然浅拷贝有这些“坑”,什么时候用它才合适?其实浅拷贝的价值,恰恰在于它不做多余工作。以下三个场景选它既快又稳。
- 基本类型数组批量读取/传递:比如int[]作为参数传入工具方法,防止被意外修改,开销仅O(1)内存分配 + O(n)内存复制
- 临时缓存只读视图:比如将数据库查询结果转成String[]后供UI层展示,后续不修改内容,clone即可隔离原始数据源
- 构建不可变包装结构:配合final字段使用,例如
public final String[] names = Arrays.copyOf(src, src.length);确保外部无法通过引用篡改内部状态
记住:浅拷贝不是偷懒,而是精准地选择“不移栽对象,只传递地址”。理解了这个边界,你就能在性能和安全之间找到最舒服的平衡点。
