跳到内容

第 1 卷 / 第 6 章 / 第 3 课

6.3 数组复制、工具与可变参数 ​

前置知识:数组声明、索引、遍历和方法

预计时间:30 分钟

完成标志:能按语义选择复制方法,正确使用排序与二分搜索,并定义可变参数的空输入策略

已经存了三个成绩,又收到两个新成绩。原数组长度不能改变,需要创建更长的数组并复制已有值。

java
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. 五种复制写法复制的都是组件值 ​

手动循环 ​

java
int[] source = {1, 2, 3};
int[] target = new int[source.length];
for (int index = 0; index < source.length; index++) {
    target[index] = source[index];
}

手动循环适合复制时还要过滤或变换。只做原样复制时,标准库更清楚。

clone ​

java
int[] copy = source.clone();

数组的 clone 返回同一运行时组件类型、同一长度的新数组。它是浅复制。

Arrays.copyOf 与 copyOfRange ​

java
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 ​

java
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。这不是一次失败就完全回滚的操作;需要保留原目标时,先复制到独立数组并验证。

源和目标可以是同一个数组,重叠区域按“仿佛先复制到临时数组”的语义处理:

java
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. 数组独立不等于元素对象独立 ​

java
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]); // AB

first 和 second 是两个数组对象,但组件中最初保存着同一个 StringBuilder 引用。修改该对象,两边都能观察到;替换 second[0] 只改变第二个数组的组件。

“深复制”没有适用于所有对象的通用实现。需要先定义对象身份、共享关系、循环引用和不可变对象是否复用,再为领域模型实现复制策略。

3. Arrays 工具类的常用契约 ​

java
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); // -4

binarySearch 要求待搜索范围已按同一种顺序排序。否则结果没有可依赖的含义。找不到键时返回 -(插入点) - 1,所以 -4 对应插入点 3。若存在重复键,不应依赖返回其中哪一个位置。

更多常用操作:

java
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)首个不同位置,无差异为 -1Java 9 起提供
toString(array)一维数组可读文本嵌套数组用 deepToString

直接拼接数组对象时常见 [I@... 一类字符串。这是数组继承的对象字符串形式,包含类型和身份哈希的表示;它不是数组内容,也不能当作承诺的内存地址。输出组件请用 Arrays.toString。

4. 可变参数在方法体内就是数组 ​

java
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;
}

调用方式:

java
average(85, 92, 78);
average(new int[]{90, 95});
// average(); // 本文契约下抛 IllegalArgumentException

规则:

  1. 一个方法声明最多有一个可变参数。
  2. 可变参数必须排在形参列表最后。
  3. 调用时可以传 0 个、1 个或多个实参,也可以直接传兼容数组。
  4. 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。

→ 下一步:多维数组

API 入口:Java 17 Arrays 文档、Java 17 System.arraycopy。

Built with VitePress | Software Systems Atlas