Java 中主要有两种方式来实现 foreach 风格的循环:
- 增强 for 循环 (Enhanced for loop):这是最常用、最推荐的方式,从 Java 5 (JDK 1.5) 开始引入。
- 使用 Stream API:这是一种更现代、函数式的方式,从 Java 8 开始引入,功能更强大,尤其是在进行复杂的数据处理时。
增强 for 循环 (Enhanced for loop)
这是最直接、最简洁的 foreach 实现,语法非常清晰。
语法
for (元素类型 元素变量 : 遍历对象) {
// 循环体,在这里可以访问元素变量
}
- 元素类型:数组中每个元素的类型。
- 元素变量:一个临时变量,在每次循环中,它会自动接收数组中的一个元素的值。
- 遍历对象:要遍历的数组。
示例
下面是一个完整的例子,演示如何遍历一个 String 数组和一个 int 数组。
public class ForeachArrayExample {
public static void main(String[] args) {
// 1. 遍历 String 数组
String[] fruits = {"Apple", "Banana", "Cherry", "Date"};
System.out.println("--- 遍历水果数组 ---");
// 使用增强 for 循环
for (String fruit : fruits) {
System.out.println("水果: " + fruit);
}
System.out.println("\n--- 遍历数字数组 ---");
// 2. 遍历 int 数组
int[] numbers = {10, 20, 30, 40, 50};
// 使用增强 for 循环
for (int number : numbers) {
System.out.println("数字: " + number);
}
}
}
输出结果:
--- 遍历水果数组 ---
水果: Apple
水果: Banana
水果: Cherry
水果: Date
--- 遍历数字数组 ---
数字: 10
数字: 20
数字: 30
数字: 40
数字: 50
优点
- 代码简洁:语法简单,易于阅读,避免了传统
for循环中复杂的索引管理。 - 不易出错:不需要关心数组的起始索引、结束索引和索引递增,减少了
ArrayIndexOutOfBoundsException的风险。 - 专注于元素本身:让你更专注于对每个元素进行什么操作,而不是如何获取元素。
缺点(与传统 for 循环对比)
增强 for 循环虽然方便,但它有一个重要的限制:你无法获取当前元素的索引,也无法在遍历过程中修改数组本身(只能修改元素的副本)。
场景对比:
| 场景 | 增强型 for 循环 |
传统 for 循环 |
|---|---|---|
| 遍历并打印元素 | 非常适合 | 也可以,但稍显冗长 |
| 需要知道元素的索引 | 无法做到 | 非常适合 (i 就是索引) |
| 需要修改数组中的元素 | 只能修改元素的副本,无法影响原数组 | 非常适合 (array[i] = newValue;) |
| 需要反向遍历 | 无法做到 | 非常适合 (for (int i = array.length - 1; i >= 0; i--)) |
| 需要跳过某些元素 | 无法做到 | 非常适合 (if (i % 2 == 0) continue;) |
示例:无法修改原数组
int[] scores = {95, 88, 76, 100};
// 使用增强 for 循环尝试修改分数
for (int score : scores) {
score = 0; // 这只是修改了 score 这个临时变量的值,原数组 scores 中的元素不变
}
System.out.println(Arrays.toString(scores)); // 输出: [95, 88, 76, 100]
使用传统 for 循环修改原数组:
int[] scores = {95, 88, 76, 100};
// 使用传统 for 循环修改分数
for (int i = 0; i < scores.length; i++) {
scores[i] = 0; // 直接通过索引修改原数组
}
System.out.println(Arrays.toString(scores)); // 输出: [0, 0, 0, 0]
使用 Stream API (Java 8+)
对于更复杂的场景,比如过滤、转换、聚合等,使用 Stream API 会非常强大和优雅。
示例
import java.util.Arrays;
public class StreamArrayExample {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
System.out.println("--- 使用 Stream API 遍历 ---");
// 1. 简单遍历
Arrays.stream(numbers).forEach(number -> System.out.println("数字: " + number));
System.out.println("\n--- Stream API 高级操作示例 ---");
// 2. 过滤出偶数并打印
System.out.println("所有偶数:");
Arrays.stream(numbers)
.filter(n -> n % 2 == 0) // 过滤条件
.forEach(System.out::println); // 方法引用,等同于 n -> System.out.println(n)
// 3. 计算所有偶数的和
int sumOfEvens = Arrays.stream(numbers)
.filter(n -> n % 2 == 0)
.sum();
System.out.println("\n所有偶数的和是: " + sumOfEvens);
}
}
输出结果:
--- 使用 Stream API 遍历 ---
数字: 1
数字: 2
数字: 3
数字: 4
数字: 5
数字: 6
数字: 7
数字: 8
数字: 9
数字: 10
--- Stream API 高级操作示例 ---
所有偶数:
2
4
6
8
10
所有偶数的和是: 30
Stream API 的优点
- 函数式编程:代码更声明式,你只需要“做什么”,而不需要关心“怎么做”。
- 强大的链式操作:可以像流水线一样组合多个操作(如
filter,map,sorted,forEach等)。 - 并行处理:只需将
stream()换成parallelStream(),就可以轻松实现并行计算,提高大数据量下的处理速度。
Stream API 的缺点
- 开销较大:对于简单的遍历,Stream API 的性能开销比增强 for 循环要大。
- 代码稍复杂:对于初学者,其语法可能不如增强 for 循环直观。
总结与选择
| 特性 | 增强型 for 循环 |
Stream API | 传统 for 循环 |
|---|---|---|---|
| 简洁性 | ⭐⭐⭐⭐⭐ (最高) | ⭐⭐⭐ (复杂操作时高) | ⭐⭐ (需要索引时) |
| 可读性 | ⭐⭐⭐⭐⭐ (最高) | ⭐⭐⭐⭐ (函数式风格) | ⭐⭐⭐ (逻辑清晰) |
| 功能 | 仅遍历 | 遍历、过滤、转换、聚合等 | 任何操作,包括修改原数组、获取索引 |
| 性能 | ⭐⭐⭐⭐⭐ (最高) | ⭐⭐ (简单遍历时) | ⭐⭐⭐⭐ (通常很好) |
| 适用场景 | 简单的从头到尾遍历,不关心索引 | 复杂的数据处理、函数式风格 | 需要索引、需要修改原数组、需要复杂控制逻辑 |
如何选择?
-
如果你只是想简单地遍历数组中的每一个元素,不关心索引,也不需要修改数组本身:
- 首选增强型
for循环,它最简单、最安全、性能最好。
- 首选增强型
-
如果你需要对数组元素进行复杂的操作,比如过滤、转换、计算总和、查找最大值等:
- 使用 Stream API,它更强大、更灵活,代码更优雅。
-
如果你在遍历过程中需要知道元素的索引,或者需要修改数组中的元素:
- 使用传统
for循环,这是唯一能直接满足这些需求的标准方式。
- 使用传统
