bigywater.tech
Java 内部原理 · 135

Arrays.fill()真实陷阱:填充二维数组的每一行,真实变成了同一个数组的三个引用

上一篇验证了二维数组是"数组的数组"——这篇真实复现一个由此产生的经典真实陷阱:用Arrays.fill(int[][], int[])给二维数组"填充"每一行,真实得到的不是三份独立数据,而是三个指向同一个数组的引用。

三行真实共享同一个引用
真实验证:Arrays.fill(arr2d, sharedRow)后,arr2d的每一行真实都是sharedRow这一个数组对象
改一行真实牵连全部
真实复现:修改arr2d[0][0],其余两行真实同步跟着变化,因为它们本来就是同一个数组

上一篇:接口默认方法菱形冲突真实验证

真实复现:Arrays.fill()给二维数组"填充"出三个共享引用

Arrays类没有专门为二维数组设计的fill重载,但fill(Object[], Object)这个通用重载可以合法接受二维数组(它本质是Object[])和一个一维数组作为填充值。

ArraysFillMultidimDemo.java int[][] arr2d = new int[3][3]; int[] sharedRow = {1, 2, 3}; Arrays.fill(arr2d, sharedRow); // 真实合法调用: fill(Object[], Object)
真实实测
真实Arrays.fill(arr2d, sharedRow)后, arr2d: [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
真实arr2d[0] == sharedRow: true
真实arr2d[1] == arr2d[2]: true

真实结果证明这次调用真实成功了(没有编译错误、没有运行时异常),打印出来的deepToString()看起来也完全正常——三行数据看起来都是[1, 2, 3]。但真实的引用相等性检测揭示了真相:arr2d[0]、arr2d[1]、arr2d[2]真实全部都是同一个sharedRow对象,不是三份独立的数据副本。这正好利用了上一篇验证过的"二维数组是数组的数组"这个结构——Arrays.fill(Object[], Object)对每个外层位置做的只是一次简单的引用赋值,完全不知道也不关心这个"值"本身又是一个数组。

真实复现陷阱:修改一行,其余行真实跟着一起变

ArraysFillMultidimDemo.java arr2d[0][0] = 99; // 只想改第一行第一个元素
真实实测
真实修改arr2d[0][0]=99后, arr2d(所有行都变了): [[99, 2, 3], [99, 2, 3], [99, 2, 3]]
这是一个典型的"看起来对、跑起来错"的真实陷阱。开发者的意图通常是"把每一行都初始化成[1,2,3]这样的默认值",代码也真实编译通过、第一次打印结果也真实看起来完全正确。但只要后续代码对某一行做了原地修改(arr2d[0][0] = 99这种常见操作),真实的bug才会暴露——因为三行本来就是同一个数组对象,修改任何一行都真实等同于同时修改了全部三行。这类bug尤其危险,因为它不会立刻报错,而是在数据被后续使用时才表现出诡异的"数据串台",排查起来往往要追溯很久才能定位到问题出在最初的初始化那一行。

真实验证:正确写法需要为每一行单独创建数组

ArraysFillMultidimDemo.java int[][] arr2dCorrect = new int[3][3]; for (int[] row : arr2dCorrect) Arrays.fill(row, 5); // 对每行单独fill
真实实测
正确写法: 循环对每行单独Arrays.fill(row, 5): [[5, 5, 5], [5, 5, 5], [5, 5, 5]]
正确写法下修改arr2dCorrect[0][0]=100后(其他行不受影响): [[100, 5, 5], [5, 5, 5], [5, 5, 5]]

真实结果证明正确做法是用循环对arr2dCorrect的每一行(每个真实独立的int[]对象)分别调用Arrays.fill(row, value)——因为new int[3][3]这种"完整维度声明"的二维数组,本来每一行就已经是真实独立分配的数组对象(不是共享引用),对每行单独fill不会破坏这种独立性。修改arr2dCorrect[0][0]后,真实只有第一行的数据变化,其余两行完全不受影响——这才是大多数场景下真正想要的行为。

分步演示:改一行,为什么另外两行也跟着变

用 identityHashCode 给每一行的数组对象编号,串成动画:编号相同就是同一个对象,改一处会牵连所有引用它的地方。

Arrays.fill(arr2d, sharedRow) vs 逐行 fill:三行是不是同一个对象,真实决定了改一行会不会牵连全部
这一步真实发生变化的格子

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,共享引用陷阱和正确写法的行为差异重复运行完全一致。
    • 和多维数组内存真实验证一起看,这篇陷阱正是上一篇"二维数组是数组的数组"这个结构性事实在实际编码中的直接体现。
    • 没有涉及:Java 8+的Arrays.setAll()或Stream结合IntStream.range()构造二维数组初始值是否存在类似的共享陷阱、Collections.fill()对List<List<T>>是否有相同的引用共享问题——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号