第15章

Java for-each循环详解

掌握增强for循环的语法、特点和最佳实践,学会高效遍历数组和集合

章节介绍

for-each循环(也称为增强for循环)是Java 5引入的一个重要特性,它提供了一种更简洁、更安全的方式来遍历数组和集合。本章将深入学习for-each循环的语法、特点、适用场景以及与传统for循环的区别,帮助你掌握这个强大的编程工具。

通过本章的学习,你将能够熟练使用for-each循环来处理各种数据结构,编写更加简洁和可读的代码,同时避免常见的编程错误。

学习目标

掌握for-each循环的基本语法

学会for-each循环的正确写法和使用方法

理解for-each循环的适用场景

知道什么时候使用for-each循环,什么时候使用传统for循环

学会遍历数组和集合

掌握使用for-each循环遍历各种数据结构的技巧

了解for-each循环的优缺点

理解for-each循环的限制和最佳使用场景

掌握嵌套for-each循环

学会在多维数组和复杂数据结构中使用嵌套循环

学习最佳实践和常见陷阱

避免常见错误,编写高质量的循环代码

for-each循环基础语法

基本语法结构

for-each循环的语法比传统for循环更加简洁直观:

for (元素类型 变量名 : 数组或集合) {
    // 循环体
    // 处理当前元素
}

语法要素说明

  • 元素类型:数组或集合中元素的数据类型
  • 变量名:用于存储当前遍历元素的变量
  • 冒号(:):分隔符,读作"in"
  • 数组或集合:要遍历的数据结构

简单示例

// 遍历整数数组
int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
    System.out.println(number);
}

// 遍历字符串数组
String[] fruits = {"苹果", "香蕉", "橙子"};
for (String fruit : fruits) {
    System.out.println("我喜欢吃: " + fruit);
}
💻 查看完整代码 - 在线IDE体验

for-each循环 vs 传统for循环

对比分析

特性 for-each循环 传统for循环
代码简洁性 ✅ 更简洁,减少样板代码 ❌ 相对复杂,需要管理索引
只读遍历 ✅ 推荐使用 ✅ 可以使用
获取索引 ❌ 不支持 ✅ 支持
修改元素 ❌ 不支持 ✅ 支持
反向遍历 ❌ 不支持 ✅ 支持
同时遍历多个数组 ❌ 不支持 ✅ 支持
避免数组越界 ✅ 自动处理 ❌ 需要手动控制
性能 ✅ 通常更好 ✅ 相当

代码对比示例

int[] numbers = {10, 20, 30, 40, 50};

// 传统for循环
System.out.println("传统for循环:");
for (int i = 0; i < numbers.length; i++) {
    System.out.print(numbers[i] + " ");
}

// for-each循环
System.out.println("\nfor-each循环:");
for (int number : numbers) {
    System.out.print(number + " ");
}

for-each循环的优点

  • 代码更简洁、可读性更好
  • 避免数组越界错误
  • 不需要手动管理索引
  • 编译器可以进行更好的优化
  • 减少出错的可能性
  • 意图更明确,专注于处理元素

for-each循环的限制

  • 无法获取当前元素的索引
  • 无法修改数组或集合中的元素
  • 无法反向遍历
  • 无法同时遍历多个数组
  • 无法在遍历中修改集合结构
  • 只能从头到尾顺序遍历

数组和集合遍历

数组遍历

for-each循环可以遍历所有类型的数组:

// 基本数据类型数组
int[] intArray = {1, 2, 3, 4, 5};
for (int value : intArray) {
    System.out.println(value);
}

// 对象数组
String[] stringArray = {"Java", "Python", "JavaScript"};
for (String language : stringArray) {
    System.out.println("编程语言: " + language);
}

// 二维数组
int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
for (int[] row : matrix) {
    for (int element : row) {
        System.out.print(element + " ");
    }
    System.out.println();
}

集合遍历

for-each循环可以遍历所有实现了Iterable接口的集合:

import java.util.*;

// ArrayList遍历
List cities = new ArrayList<>();
cities.add("北京");
cities.add("上海");
cities.add("广州");

for (String city : cities) {
    System.out.println("城市: " + city);
}

// HashSet遍历
Set numbers = new HashSet<>();
numbers.add(10);
numbers.add(20);
numbers.add(30);

for (Integer number : numbers) {
    System.out.println("数字: " + number);
}

// HashMap遍历
Map scores = new HashMap<>();
scores.put("张三", 85);
scores.put("李四", 92);

// 遍历键值对
for (Map.Entry entry : scores.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// 只遍历键
for (String name : scores.keySet()) {
    System.out.println("学生: " + name);
}

// 只遍历值
for (Integer score : scores.values()) {
    System.out.println("成绩: " + score);
}

提示

for-each循环适用于所有实现了Iterable接口的类,包括List、Set、Queue等集合类,但不能直接遍历Map,需要通过Map的视图方法(keySet()、values()、entrySet())来遍历。

嵌套for-each循环

二维数组遍历

使用嵌套for-each循环可以优雅地处理多维数组:

// 创建一个3x4的二维数组
int[][] matrix = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

// 使用嵌套for-each循环遍历
System.out.println("矩阵内容:");
for (int[] row : matrix) {
    for (int element : row) {
        System.out.printf("%4d", element);
    }
    System.out.println(); // 换行
}

// 计算矩阵所有元素的和
int sum = 0;
for (int[] row : matrix) {
    for (int element : row) {
        sum += element;
    }
}
System.out.println("矩阵元素总和: " + sum);

集合的集合

处理复杂的嵌套数据结构:

// 创建班级学生信息
List> classes = new ArrayList<>();

// 添加各班学生
classes.add(Arrays.asList("张三", "李四", "王五"));
classes.add(Arrays.asList("赵六", "钱七", "孙八", "周九"));
classes.add(Arrays.asList("吴十", "郑十一"));

// 遍历所有班级和学生
int classNumber = 1;
for (List classStudents : classes) {
    System.out.println("第" + classNumber++ + "班:");
    for (String student : classStudents) {
        System.out.println("  - " + student);
    }
}

// 统计总学生数
int totalStudents = 0;
for (List classStudents : classes) {
    totalStudents += classStudents.size();
}
System.out.println("总学生数: " + totalStudents);

Map中的集合

// 商店商品分类
Map> store = new HashMap<>();
store.put("水果", Arrays.asList("苹果", "香蕉", "橙子"));
store.put("蔬菜", Arrays.asList("白菜", "萝卜", "土豆"));
store.put("肉类", Arrays.asList("猪肉", "牛肉"));

// 遍历商品分类
for (Map.Entry> category : store.entrySet()) {
    System.out.println(category.getKey() + "类:");
    for (String product : category.getValue()) {
        System.out.println("  - " + product);
    }
}

常见陷阱和错误

陷阱1:在for-each循环中修改集合结构

这是最常见的错误,会导致ConcurrentModificationException异常。

// ❌ 错误做法:在for-each循环中删除元素
List fruits = new ArrayList<>(Arrays.asList("苹果", "香蕉", "橙子"));

// 这会抛出ConcurrentModificationException
try {
    for (String fruit : fruits) {
        if (fruit.equals("香蕉")) {
            fruits.remove(fruit); // 危险操作!
        }
    }
} catch (ConcurrentModificationException e) {
    System.out.println("错误:在for-each循环中修改集合结构");
}

// ✅ 正确做法:使用Iterator
Iterator iterator = fruits.iterator();
while (iterator.hasNext()) {
    String fruit = iterator.next();
    if (fruit.equals("香蕉")) {
        iterator.remove(); // 安全操作
    }
}

陷阱2:误以为可以修改数组元素

在for-each循环中修改变量不会影响原数组。

int[] numbers = {1, 2, 3, 4, 5};
System.out.println("原始数组: " + Arrays.toString(numbers));

// ❌ 错误理解:这不会修改原数组
for (int num : numbers) {
    num *= 2; // 这只是修改了局部变量
}
System.out.println("for-each循环后: " + Arrays.toString(numbers)); // 数组未改变

// ✅ 正确做法:使用传统for循环修改数组
for (int i = 0; i < numbers.length; i++) {
    numbers[i] *= 2;
}
System.out.println("传统for循环后: " + Arrays.toString(numbers));

陷阱3:对null集合使用for-each

对null引用使用for-each循环会抛出NullPointerException。

List nullList = null;

// ❌ 错误做法:直接使用for-each(会抛出NullPointerException)
try {
    for (String item : nullList) {
        System.out.println(item);
    }
} catch (NullPointerException e) {
    System.out.println("错误:对null集合使用for-each");
}

// ✅ 正确做法:先检查null
if (nullList != null) {
    for (String item : nullList) {
        System.out.println(item);
    }
} else {
    System.out.println("集合为null,跳过遍历");
}

for-each循环最佳实践

优先使用for-each

对于只读遍历,优先选择for-each循环,代码更简洁安全。

使用有意义的变量名

选择描述性强的变量名,提高代码可读性。

处理null值

在使用for-each循环前检查集合是否为null。

避免复杂计算

将复杂的计算逻辑提取到循环外部,提高性能。

合理选择循环类型

根据需求选择合适的循环类型,不要强行使用for-each。

注意性能影响

对于大数据量,考虑循环的性能影响和优化策略。

性能考虑

数组遍历性能

对于数组,for-each循环和传统for循环的性能基本相当:

int[] largeArray = new int[1000000];
for (int i = 0; i < largeArray.length; i++) {
    largeArray[i] = i;
}

// 测试for-each循环性能
long startTime = System.nanoTime();
long sum1 = 0;
for (int value : largeArray) {
    sum1 += value;
}
long forEachTime = System.nanoTime() - startTime;

// 测试传统for循环性能
startTime = System.nanoTime();
long sum2 = 0;
for (int i = 0; i < largeArray.length; i++) {
    sum2 += largeArray[i];
}
long traditionalTime = System.nanoTime() - startTime;

System.out.println("for-each循环时间: " + forEachTime + " 纳秒");
System.out.println("传统for循环时间: " + traditionalTime + " 纳秒");

集合遍历性能

对于集合,for-each循环通常性能更好,特别是对于LinkedList等非随机访问的集合:

// ArrayList:两种方式性能相当
List arrayList = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
    arrayList.add(i);
}

// LinkedList:for-each循环性能更好
List linkedList = new LinkedList<>();
for (int i = 0; i < 10000; i++) {
    linkedList.add(i);
}

// ✅ 推荐:for-each循环
for (Integer value : linkedList) {
    // 处理元素
}

// ❌ 不推荐:传统for循环(对LinkedList性能很差)
for (int i = 0; i < linkedList.size(); i++) {
    Integer value = linkedList.get(i); // O(n)操作!
}

性能提示

  • 对于数组,两种循环性能相当,选择for-each提高可读性
  • 对于ArrayList,两种循环性能相当
  • 对于LinkedList,for-each循环性能明显更好
  • 对于Set和其他集合,使用for-each循环
  • 避免在循环中进行复杂的计算和对象创建

实际应用场景

数据处理

// 计算学生平均分
List scores = Arrays.asList(85, 92, 78, 96, 88);
double sum = 0;
for (Integer score : scores) {
    sum += score;
}
double average = sum / scores.size();
System.out.printf("平均分: %.2f\n", average);

// 查找最高分
int maxScore = scores.get(0);
for (Integer score : scores) {
    if (score > maxScore) {
        maxScore = score;
    }
}
System.out.println("最高分: " + maxScore);

字符串处理

// 统计字符串长度
List words = Arrays.asList("Java", "Python", "JavaScript", "C++");
int totalLength = 0;
for (String word : words) {
    totalLength += word.length();
}
System.out.println("总字符数: " + totalLength);

// 查找包含特定字符的字符串
String searchChar = "a";
List matchingWords = new ArrayList<>();
for (String word : words) {
    if (word.toLowerCase().contains(searchChar)) {
        matchingWords.add(word);
    }
}
System.out.println("包含'" + searchChar + "'的单词: " + matchingWords);

对象处理

// 学生类示例
class Student {
    private String name;
    private int age;
    private double score;
    
    // 构造函数和getter方法...
}

// 处理学生列表
List students = Arrays.asList(
    new Student("张三", 20, 85.5),
    new Student("李四", 22, 92.0),
    new Student("王五", 19, 78.5)
);

// 查找优秀学生
System.out.println("优秀学生名单(90分以上):");
for (Student student : students) {
    if (student.getScore() >= 90) {
        System.out.println("- " + student.getName() + ": " + student.getScore());
    }
}

// 计算平均年龄
double totalAge = 0;
for (Student student : students) {
    totalAge += student.getAge();
}
double averageAge = totalAge / students.size();
System.out.printf("平均年龄: %.1f岁\n", averageAge);

章节总结

核心要点

  • 语法简洁:for-each循环提供了更简洁的遍历语法
  • 安全性高:自动处理数组边界,避免越界错误
  • 适用范围:支持数组和所有实现Iterable接口的集合
  • 性能优秀:通常与传统for循环性能相当或更好
  • 有限制性:无法获取索引、修改元素或反向遍历

使用建议

  • 优先使用for-each循环进行只读遍历
  • 需要索引或修改元素时使用传统for循环
  • 注意处理null值和集合修改的陷阱
  • 选择有意义的变量名提高代码可读性
  • 根据具体需求选择合适的循环类型

下一步学习

掌握了for-each循环后,建议继续学习:

  • while循环和do-while循环
  • 循环控制语句(break和continue)
  • Java 8的Stream API和Lambda表达式
  • 集合框架的深入使用