如何利用 Arrays.fill() 快速为数组所有元素赋相同的初始值
Arrays.fill()借助JVM内联优化,对基本类型数组执行内存块拷贝,彻底消除手写for循环的越界、漏判风险及边界检查开销,性能更优且代码更可靠。它是数组批量初始化的高效手段,避免手动循环的常见编程错误。
先放一个结论:如果你还在手写 for 循环去给数组赋值,那多半是时候换个思路了。Arrays.fill() 这个工具,底层直接由 JVM 做了 intrinsic 内联优化,基本类型数组走的是内存块拷贝,天然避免了手写循环常见的越界、漏判、下标错误。性能上,对基本类型也是实打实的更优。

为什么说 Arrays.fill() 比 for 循环更可靠?
关键在于它绕开了 Ja va 层面的边界检查。对于基本类型数组,比如 int[] 或 double[],底层直接调用 ArraysSupport.fill,循环和边界判断的开销完全被消除。而自己手写 for 循环呢?稍不留神,i <= arr.length 还是 i < arr.length?写错了就是 ArrayIndexOutOfBoundsException。
对于引用类型数组,Arrays.fill() 还有一个容易被忽略的好处:它可以帮你规避“浅拷贝陷阱”。举个例子,new String[10] 之后想把每一项设为 "N/A",用 Arrays.fill(arr, "N/A") 非常安全。但如果是手动赋值,逻辑上虽然没错,可一旦中间加了条件判断或者提前 break,漏填就几乎是必然的事。
重载版本那么多,常用的是哪几种?参数别搞混
实际开发中,绝大多数场景只需要记住三个版本:
Arrays.fill(int[] a, int val):最常用,整个int[]填一遍。Arrays.fill(double[] a, int fromIndex, int toIndex, double val):只填指定区间。注意toIndex是**不包含**的,区间是[fromIndex, toIndex),这个边界特性非常容易踩坑。Arrays.fill(Object[] a, Object val):填对象数组。但你必须清楚,val是个引用,结果就是数组里的所有元素指向同一个对象。
别用 Arrays.fill(byte[], byte) 去填 char[]——类型不匹配,编译就过不了。也别指望 Arrays.fill(boolean[], true) 能填 Boolean[],后者是引用类型,得用对应的对象版本。
引用类型数组填 null 或新对象时的陷阱
这部分是 bug 最密集的区域,值得多花点时间聊清楚:
- 填
null是安全的。Arrays.fill(strArr, null)的结果是每个元素都是独立的null,互不影响。 - 填同一个对象就很危险了。
Arrays.fill(listArr, new ArrayList())之后,所有数组元素指向**同一个**ArrayList实例。你调用任意一个add(),所有元素都会受影响。 - 如果需要每个元素都是独立的实例,那就必须用循环手动创建:
for (int i = 0; i < listArr.length; i++) listArr[i] = new ArrayList();
一个很典型的错误现象:listArr[0].add("x"); 之后,System.out.println(listArr[1].size()); 输出居然是 1。出现这个问题的根本原因,就是误用了 Arrays.fill() 去填充可变对象。
性能差异在什么规模下才值得关注?
小数组上差距不大,但一旦数组规模上来,情况就不同了。在 JDK 17+ 的 HotSpot 上,Arrays.fill() 对基本类型的处理会自动向量化,实测下来平均快 2–3 倍。如果只是初始化一次,这点差距确实可以忽略;但如果是在高频循环里反复调用,比如每帧需要清空缓冲区,那切换成 Arrays.fill() 就很有必要了。
话说回来,Arrays.fill() 本身不支持并行——它不拆分任务,也不会用 ForkJoinPool。如果真要并行填充超大数组(比如百万级的 float[]),那就得自己动手,用 ForkJoinPool.commonPool().submit(...) 分段处理,或者改用 Arrays.setAll() 配合 lambda。不过后者已经不属于“相同初始值”的应用场景了。
最后提醒一个容易被忽略的细节:Arrays.fill() 不会触发 GC,也不会校验 val 是否为 null。对对象数组来说,传错引用也不会立刻报错,问题会一直延后到实际使用时才暴露——这才是最让人头疼的地方。

































