6.3 数组复制、工具与可变参数
前置知识:数组声明、索引、遍历和方法
预计时间:30 分钟
完成标志:能按语义选择复制方法,正确使用排序与二分搜索,并定义可变参数的空输入策略
已经存了三个成绩,又收到两个新成绩。原数组长度不能改变,需要创建更长的数组并复制已有值。
int[] oldScores = {85, 92, 78};
int[] newScores = java.util.Arrays.copyOf(oldScores, 5);
newScores[3] = 95;
newScores[4] = 88;oldScores 仍引用长度为 3 的旧数组,newScores 引用长度为 5 的新数组。ArrayList 会在容量不足时管理类似的重新分配过程,但增长策略属于实现细节,留到集合章节。
1. 五种复制写法复制的都是组件值
手动循环
int[] source = {1, 2, 3};
int[] target = new int[source.length];
for (int index = 0; index < source.length; index++) {
target[index] = source[index];
}手动循环适合复制时还要过滤或变换。只做原样复制时,标准库更清楚。
clone
int[] copy = source.clone();数组的 clone 返回同一运行时组件类型、同一长度的新数组。它是浅复制。
Arrays.copyOf 与 copyOfRange
import java.util.Arrays;
int[] sameLength = Arrays.copyOf(source, source.length);
int[] longer = Arrays.copyOf(source, 5); // [1, 2, 3, 0, 0]
int[] shorter = Arrays.copyOf(source, 2); // [1, 2]
int[] slice = Arrays.copyOfRange(source, 1, 3); // [2, 3]copyOf 可截断或用组件默认值补齐。copyOfRange 使用左闭右开区间 [from, to);from 必须位于原数组合法范围或正好等于长度,to 可以大于原长度,此时尾部补默认值,但不能小于 from。
System.arraycopy
int[] source = {10, 20, 30, 40};
int[] target = new int[6];
System.arraycopy(source, 1, target, 2, 3);
// target: [0, 0, 20, 30, 40, 0]参数依次是源数组、源起点、目标数组、目标起点和组件数量。范围必须同时落在两个数组内;负下标、负数量或越过任一端点会失败。引用数组还可能因运行时组件类型不兼容而抛 ArrayStoreException。基本类型数组要求源、目标组件类型相同;int[] 不能借此自动拓宽成 long[]。
复制引用数组时,如果前几个元素兼容、后一个不兼容,前面的元素可能已经写进目标数组,再抛 ArrayStoreException。这不是一次失败就完全回滚的操作;需要保留原目标时,先复制到独立数组并验证。
源和目标可以是同一个数组,重叠区域按“仿佛先复制到临时数组”的语义处理:
int[] values = {1, 2, 3, 4, 5};
System.arraycopy(values, 0, values, 1, 4);
System.out.println(Arrays.toString(values));
// [1, 1, 2, 3, 4]不要用“哪个永远最快”作为选择依据。先按是否需要区间、变换、改长度和重叠复制选择 API;只有性能成为已测量瓶颈时,才在目标 JVM 和真实数据规模上做基准测试。
2. 数组独立不等于元素对象独立
StringBuilder[] first = {new StringBuilder("A")};
StringBuilder[] second = first.clone();
second[0].append("B");
System.out.println(first[0]); // AB
second[0] = new StringBuilder("C");
System.out.println(first[0]); // ABfirst 和 second 是两个数组对象,但组件中最初保存着同一个 StringBuilder 引用。修改该对象,两边都能观察到;替换 second[0] 只改变第二个数组的组件。
“深复制”没有适用于所有对象的通用实现。需要先定义对象身份、共享关系、循环引用和不可变对象是否复用,再为领域模型实现复制策略。
3. Arrays 工具类的常用契约
import java.util.Arrays;
int[] numbers = {42, 7, 15, 8, 23, 3};
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers));
// [3, 7, 8, 15, 23, 42]
int found = Arrays.binarySearch(numbers, 15);
System.out.println(found); // 3
int missing = Arrays.binarySearch(numbers, 9);
System.out.println(missing); // -4binarySearch 要求待搜索范围已按同一种顺序排序。否则结果没有可依赖的含义。找不到键时返回 -(插入点) - 1,所以 -4 对应插入点 3。若存在重复键,不应依赖返回其中哪一个位置。
更多常用操作:
int[] filled = new int[4];
Arrays.fill(filled, 7); // [7, 7, 7, 7]
int[] left = {1, 2, 3};
int[] right = {1, 4, 3};
System.out.println(Arrays.equals(left, right)); // false
System.out.println(Arrays.mismatch(left, right)); // 1| 方法 | 结果 | 重要边界 |
|---|---|---|
sort(array) | 原地按自然顺序排序 | 修改原数组 |
binarySearch(array, key) | 索引或负的插入点编码 | 搜索前必须按相同顺序排序 |
fill(array, value) | 用同一组件值填充 | 引用数组填入的是同一个引用值 |
equals(a, b) | 比较一维数组组件 | 嵌套数组通常用 deepEquals |
mismatch(a, b) | 首个不同位置,无差异为 -1 | Java 9 起提供 |
toString(array) | 一维数组可读文本 | 嵌套数组用 deepToString |
直接拼接数组对象时常见 [I@... 一类字符串。这是数组继承的对象字符串形式,包含类型和身份哈希的表示;它不是数组内容,也不能当作承诺的内存地址。输出组件请用 Arrays.toString。
4. 可变参数在方法体内就是数组
static double average(int... numbers) {
if (numbers.length == 0) {
throw new IllegalArgumentException("no numbers");
}
long sum = 0L;
for (int number : numbers) {
sum = Math.addExact(sum, number);
}
return (double) sum / numbers.length;
}调用方式:
average(85, 92, 78);
average(new int[]{90, 95});
// average(); // 本文契约下抛 IllegalArgumentException规则:
- 一个方法声明最多有一个可变参数。
- 可变参数必须排在形参列表最后。
- 调用时可以传 0 个、1 个或多个实参,也可以直接传兼容数组。
int...与int[]在方法签名上不能形成两个重载,因为它们对应同一种形参类型。
average((int[]) null) 会把 null 作为整个数组参数传入,随后访问 length 时失败。不要用 null 表示“没有可变参数”,直接不传参数,并让方法明确处理长度 0。
若同时重载 show(int...) 和 show(String...),调用 show(null) 会产生重载歧义;类型转换可以消除歧义,但更好的 API 往往是避免这组重载。
专业速查
| 任务 | 首选 | 原因 |
|---|---|---|
| 完整浅复制 | clone() 或 Arrays.copyOf | 直接表达新数组 |
| 改变副本长度 | Arrays.copyOf | 明确截断或补默认值 |
| 复制左闭右开片段 | Arrays.copyOfRange | 返回新数组 |
| 复制到既有目标位置 | System.arraycopy | 控制源、目标区间并支持重叠 |
| 复制时变换 | 索引循环 | 每个组件可执行自定义逻辑 |
| 查找已排序数组 | Arrays.binarySearch | 先满足排序前置条件 |
离开这一页前
分别预测 copyOf 扩长、截短和 arraycopy 重叠复制的结果,再画出引用数组浅复制后的共享元素对象。二分搜索找不到值时,亲手把负返回值还原成插入点;可变参数则要同时测试 0 个参数、显式数组和强制转换后的 null。
→ 下一步:多维数组