跳到内容

第 1 卷 / 第 6 章 / 第 2 课

6.2 数组索引与遍历 ​

前置知识:数组声明、索引边界和循环

预计时间:20 分钟

完成标志:能根据是否需要索引、修改组件或倒序访问来选择遍历方式

scores 有 85、92、78、95、88 五个成绩。手写五条输出还能应付,但数组一变长,就该让循环负责索引。

1. 索引循环给出位置和值 ​

java
int[] scores = {85, 92, 78, 95, 88};

for (int index = 0; index < scores.length; index++) {
    System.out.println("第 " + (index + 1)
            + " 个人的分数:" + scores[index]);
}

输出:

text
第 1 个人的分数:85
第 2 个人的分数:92
第 3 个人的分数:78
第 4 个人的分数:95
第 5 个人的分数:88

循环条件使用 index < scores.length。当索引等于长度时,循环结束,不会执行越界访问。空数组的长度为 0,第一次检查条件就失败,因此循环体执行 0 次。

需要原地修改组件时也用索引:

java
for (int index = 0; index < scores.length; index++) {
    scores[index] = Math.min(100, scores[index] + 5);
}

这里假设输入成绩已经校验为 0 到 100,再把加分结果限制到 100。若业务允许超过 100,应删除这个规则;代码不能替业务偷偷决定边界。

2. 增强 for 适合只关心值 ​

java
for (int score : scores) {
    System.out.println("分数:" + score);
}

增强 for 按顺序取出每个组件的值,并把它赋给循环变量 score。它不会直接暴露索引,适合读取、统计和调用只需元素值的方法。

给循环变量重新赋值不会替换数组组件:

java
int[] values = {1, 2, 3};
for (int value : values) {
    value = 0;
}
System.out.println(java.util.Arrays.toString(values));
// [1, 2, 3]

说“循环变量是副本”时要留意组件类型:

  • 对 int[],复制的是整数值。
  • 对 String[]、Person[] 等引用类型数组,复制的是引用值。重新指定循环变量不会替换数组组件,但通过该引用修改可变对象,数组中的引用仍会观察到同一个对象。

Java 的参数传递也是同样规律:始终按值传递。把数组传给方法时,复制的是数组引用值。方法可以通过复制的引用修改组件,却不能仅靠给参数重新赋值来替换调用方变量。

java
static void changeFirst(int[] values) {
    values[0] = 99;
}

static void replaceLocally(int[] values) {
    values = new int[]{7, 8, 9}; // 只改变参数变量
}

调用 changeFirst(original) 会修改 original[0];调用 replaceLocally(original) 不会让 original 指向新数组。

3. 倒序与相邻访问要先推导边界 ​

倒序输出:

java
for (int index = scores.length - 1; index >= 0; index--) {
    System.out.println(scores[index]);
}

空数组时初值是 -1,条件立即失败。这里的索引是 int;若换成无符号索引类型的语言,倒序写法可能需要调整。

比较相邻元素时,循环只走到倒数第二个位置:

java
for (int index = 0; index + 1 < scores.length; index++) {
    long difference = (long) scores[index + 1] - scores[index];
    System.out.println(difference);
}

不要先背循环模板。先写出循环体会访问哪些索引,再反推使所有访问合法的条件。

4. 统计时明确空输入契约 ​

求和对空数组可以自然返回 0:

java
static long sum(int[] values) {
    long total = 0L;
    for (int value : values) {
        total = Math.addExact(total, value);
    }
    return total;
}

求平均值却要决定空数组的含义:

java
static double average(int[] values) {
    if (values.length == 0) {
        throw new IllegalArgumentException("empty values");
    }
    return (double) sum(values) / values.length;
}

若 values 允许为 null,还要在契约中说明是拒绝、当作空数组,还是使用其他含义。上面的实现让 null 产生 NullPointerException,适合把 null 视为调用方编程错误的内部 API;公共 API 常用 Objects.requireNonNull(values, "values") 给出更明确的消息。

5. 选择遍历方式 ​

需求推荐写法原因
只读取每个值增强 for不暴露无用索引
修改数组组件索引 for可写 values[index]
需要位置或相邻值索引 for位置参与计算
倒序访问索引 for明确控制起点和方向
同时处理多数组对应位置索引 for先验证长度关系

流式 API 也能处理数组,但在掌握循环、边界和副作用之前,不应把简单遍历包装成更难调试的表达式。

语言差异窗口 ​

Python 的 enumerate 同时给出索引和值:

python
scores = [85, 92, 78, 95, 88]
for index, score in enumerate(scores):
    print(f"第 {index + 1} 个人的分数:{score}")

C++17 的 range-based for 可按值或按引用绑定。for (int value : values) 修改局部值,for (int& value : values) 可修改原组件。这与 Java 增强 for 的引用模型不同,不能只按外观类比。

常见故障 ​

现象首先检查
最后一轮越界是否写成 index <= values.length
增强 for 修改无效是否只给循环变量重新赋值
空数组求平均得到异常或非数是否定义了空输入策略
方法修改了原数组是否通过复制的引用写了组件
两数组同步遍历越界是否先验证长度或按较短长度处理

离开这一页前 ​

用空数组、单元素数组和三元素数组分别走一遍正序、倒序与相邻元素循环。需要修改组件时指出索引写回的位置;使用增强 for 时则说明循环变量复制的是基本值还是引用值。求平均值还必须先决定空输入和 null 的契约。

→ 下一步:数组操作与工具

Built with VitePress | Software Systems Atlas