System.arraycopy不进行防护,仅执行内存块搬运,边界检查需调用方预先完成;参数违规会立即抛NullPointerException、NegativeArraySizeException、ArrayIndexOutOfBoundsException或ArrayStoreException;检查在调用入口统一完成,length=0合法,同数组拷贝自动处理重叠,设计目标为极致性能而非容错。

System.arraycopy 本身不做“防护”,它只做一件事:按你给的地址和长度,把内存块搬过去。边界检查不是它负责的,而是你调用前必须完成的动作——参数一错,立刻抛异常,不提示、不兜底、不静默修正。
它会直接抛哪些异常?
五个参数中任意一项违规,都会在方法入口处被 JVM 一次性校验并抛出对应异常:
- NullPointerException:src 或 dest 为 null
- NegativeArraySizeException:length < 0
-
ArrayIndexOutOfBoundsException:任一越界情形,包括
• srcPos < 0 或 srcPos + length > src.length
• destPos < 0 或 destPos + length > dest.length - ArrayStoreException:运行时类型不兼容,比如把 String[] 拷到 Integer[] 中(编译能过,运行时报)
边界检查发生在哪一步?
不是在复制过程中逐个检查,而是在调用开始时统一做一次快检。JVM 会验证所有索引与长度组合是否合法,全部通过才进入底层内存搬运;任一失败就立即中断,不执行任何拷贝动作。
- 这个检查是 native 层完成的,开销极小,但不会省略
- length = 0 是完全合法的输入,此时检查仍执行,但不触发实际搬运
- 同数组拷贝(原地移位)时,JVM 会自动识别重叠区域,并选用 memmove 语义保证数据不被覆盖
为什么不能依赖它自动容错?
因为它的设计目标就是极致性能:跳过 Java 层循环、避免重复边界判断、绕过 GC 写屏障。这种“信任调用方”的契约,换来的是比 for 循环快数倍的吞吐能力,但也意味着责任完全在你手上。
- 动态计算 srcPos 时(如 i * 2),必须同步验证 i * 2 + length ≤ src.length,不能只验 i
- 数组被截断或重建后,旧 length 值可能已失效,需重新确认可用长度
- dest 数组必须预先分配好且容量足够,它不会帮你扩容或创建新数组
推荐的安全调用方式
别在业务逻辑里反复写 if 判断。封装一个带校验的工具方法,既保留 arraycopy 的性能,又集中管控风险:
public static <T> void safeCopy(T[] src, int srcPos, T[] dest, int destPos, int length) {if (src == null || dest == null) throw new NullPointerException();
if (srcPos < 0 || destPos < 0 || length < 0)
throw new IndexOutOfBoundsException();
if (srcPos + length > src.length || destPos + length > dest.length)
throw new IndexOutOfBoundsException("copy exceeds bounds");
System.arraycopy(src, srcPos, dest, destPos, length);
}

















