数组遍历
本教程共 100 篇 · 第 25 篇 · 更新于 2026-08-05 · 约 6 分钟阅读
25. 数组遍历
本节目标:掌握用下标 for 和增强 for 两种方式把数组每个元素走一遍,并能顺手写出求最大值、求和、求平均这些经典算法。
把数组建好,下一步多半是”把里面的数挨个看一遍”。这叫遍历。两种主流写法:下标 for 和增强 for。遍历是所有数组算法的地基,求最值、求和、统计都建立在它之上。
下标 for 遍历
知道长度,用下标一个一个取:
int[] scores = {88, 92, 75, 60};
for (int i = 0; i < scores.length; i++) {
System.out.println("第" + i + "个:" + scores[i]);
}
```bash
`i < scores.length` 是铁律,确保不越界。下标遍历的好处是你知道"当前是第几个",想改值也行。length 是数组自带的属性,不用你自己数,这也是用 length 而不是硬编码数字的原因。
> [!NOTE]
> `scores.length` 是属性,不是方法,所以不带括号。`String` 的 `length()` 是方法,别搞混了。
## 增强 for 遍历
只读不写时,增强 for 更清爽:
```java
for (int s : scores) {
System.out.println(s);
}
不用管下标、不怕越界。缺点是没有下标,也不知道总共有几个(虽然可以另外数)。当你只关心”每个元素是什么”而不关心”排第几”时,它是最舒服的写法。
Tip遍历只是”看”,用增强 for;遍历中要改元素或要下标,用下标 for。这一条在数组和集合里都通用,记住能少写很多 bug。
求总和与平均值
int sum = 0;
for (int s : scores) {
sum += s;
}
double avg = (double) sum / scores.length;
System.out.println("总和:" + sum + ",平均:" + avg);
```bash
注意求平均时把 sum 强转成 double,否则整数相除会丢掉小数。这是新手常踩的坑:两个整数相除结果还是整数,1/2 得 0 而不是 0.5。
> [!WARNING]
> 整数相除丢小数是高频错误。只要想保留小数,除数或被除数至少一个强转成 double。比如 `(double) sum / len` 或 `sum / (double) len`。
## 求最大值
思路:先假设第一个最大,然后逐个比,遇到更大的就更新。
```java
int max = scores[0]; // 假设第一个最大
for (int i = 1; i < scores.length; i++) {
if (scores[i] > max) {
max = scores[i]; // 碰到更大的就替换
}
}
System.out.println("最大值:" + max);
从下标 1 开始比,因为 scores[0] 已经当成初值了。这种”先设擂主、逐个挑战”的模式在算法里叫擂台法,非常经典。
Warning求最大值别用
int max = 0当初值——万一把所有数都是负数,0 反而最大,结果就错了。正确做法是用数组里真实存在的元素(如 scores[0])当起点。
求最小值
和求最大值对称,反过来比就行:
int min = scores[0];
for (int i = 1; i < scores.length; i++) {
if (scores[i] < min) {
min = scores[i];
}
}
System.out.println("最小值:" + min);
```bash
同理,最小值初值也别硬编码一个很大的数,从数组取第一个元素当起点最稳。
## 统计满足条件的个数
```java
int count = 0;
for (int s : scores) {
if (s >= 60) {
count++; // 及格的记一个
}
}
System.out.println("及格人数:" + count);
这种”设计数器、满足条件就加一”的模式非常常见,记住它。以后过滤集合、统计日志,底层都是这套思路:遍历一遍,符合条件的就累加。
遍历中不要改数组长度
遍历数组时往里加元素或删元素,会ConcurrentModificationException。Java 的数组是定长的,不能增删,只能改值。想增删,用后面学的集合类。
// 错的示范:遍历里不能改数组长度
for (int s : scores) {
// 没有任何 API 能在这里加/删元素
}
```bash
## 增强 for 的值是副本
增强 for 里拿到的变量是数组元素的副本,改它不影响原数组。
```java
int[] nums = {1, 2, 3};
for (int n : nums) {
n = n * 2; // 改的是副本,不是数组里真正的元素
}
System.out.println(nums[0]); // 还是 1,没变
想改数组里的值,必须用下标 for,通过 nums[i] 直接操作。
Note增强 for 的”副本”机制对基本类型是直接复制值。对引用类型,复制的是引用(地址),所以修改对象的属性会真的改掉对象本身:
class Student {
String name;
Student(String name) { this.name = name; }
}
Student[] students = {new Student("Tom"), new Student("Jerry")};
for (Student s : students) {
s.name = "改了"; // 改的是对象属性,不是副本引用,所以会影响原数组
}
System.out.println(students[0].name); // 改了
```bash
## 小心空数组和空引用
下标遍历前先确认数组不是 null,且长度大于 0,否则可能抛异常。
> [!NOTE]
> 遍历空数组(length 为 0)循环体一次都不跑,是安全的。但数组变量本身是 null 时访问 `.length` 会抛 `NullPointerException`。区分"空数组"和"空引用":前者有对象只是没元素,后者连对象都没有。
```java
int[] empty = new int[0];
System.out.println(empty.length); // 0,安全
int[] nullArr = null;
System.out.println(nullArr.length); // NullPointerException!
Java 25 LTS 与老写法对照
遍历语法在 Java 25 LTS 没有变化。但老代码里还能看到这些面孔:
| 写法 | 状态 | 说明 |
|---|---|---|
for (int i=0; i<arr.length; i++) | ✅ 标准 | 下标遍历 |
for (int x : arr) | ✅ 标准 | 增强 for,Java 5+ |
for (int i=0; i<=arr.length-1; i++) | ⚠️ 啰嗦 | 能跑,但没必要 |
while (i < arr.length) 手写 | ⚠️ 啰嗦 | for 已经够用 |
Java 21 引入了 Stream API 的增强,也可以遍历数组:
import java.util.Arrays;
int[] scores = {88, 92, 75, 60};
Arrays.stream(scores).forEach(System.out::println);
```bash
这是函数式风格,初学了解就行,后面学 Stream 时会细讲。
## 遍历的性能对比
下标 for 和增强 for 在性能上几乎没差别。增强 for 编译后就是下标 for 的语法糖,JVM 优化后一模一样。不用纠结用哪个更快,按"要不要改值、要不要下标"来选就行。
真正影响性能的是遍历内部的逻辑:
```java
// 差的写法:每次循环都调 length
for (int i = 0; i < scores.length; i++) { ... }
// 好的写法:length 提到循环外
int len = scores.length;
for (int i = 0; i < len; i++) { ... }
现代 JVM 对 scores.length 有内联优化,实际差别微乎其微。但”先存 length”在超大规模循环(百万次以上)里确实能省一点。
数组反转算法
反转是遍历的经典应用——首尾对称交换:
int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length / 2; i++) {
int temp = arr[i];
arr[i] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
}
System.out.println(Arrays.toString(arr)); // [5, 4, 3, 2, 1]
```bash
只遍历前半部分,和后半部分交换。注意只跑到 `length / 2`,否则交换两次等于没换。
## 速查表
| 写法 | 适用场景 | 能改元素 | 能取下标 |
|------|----------|----------|----------|
| 下标 for | 要改值、要下标 | ✅ | ✅ |
| 增强 for | 只读遍历 | ❌ | ❌ |
| 典型算法 | 核心思路 |
|----------|----------|
| 求和/平均 | 累加,平均注意强转 double |
| 求最值 | 初值用第一个元素,擂台法比较 |
| 计数 | 满足条件就 count++ |
| 查找 | 遍历匹配,找到可 break |
| 过滤 | 把符合条件的收集到新数组 |
| 反转 | 首尾对称交换,只跑一半 |
## 常见疑问
**Q:增强 for 能 break 提前退出吗?**
能。`break` 和 `continue` 在两种 for 里都一样用。找到目标就可以 break,不用傻傻跑完。
**Q:数组遍历用 `i <= length - 1` 可以吗?**
能跑,但没人这么写。`i < length` 更简洁直观。别给自己找麻烦。
**Q:二维数组怎么遍历?**
外层 for 走行,内层 for 走列。下节会详细讲。
**Q:遍历数组时能删元素吗?**
数组是定长的,没有删元素的方法。想"删"只能把后面的元素往前挪,或者用 ArrayList 的 `remove`。
**Q:遍历和迭代有什么区别?**
在 Java 语境里,"遍历"和"迭代"基本同义。严格来说,迭代(iteration)更强调"用迭代器(Iterator)遍历集合",而遍历(traversal)更通用。数组只能用 for 遍历,集合既可以用 for,也可以用迭代器。