杰瑞科技汇

ArrayList遍历有几种方式?各有什么区别?

假设我们有以下 ArrayList 作为示例:

ArrayList遍历有几种方式?各有什么区别?-图1
(图片来源网络,侵删)
import java.util.ArrayList;
import java.util.List;
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");

传统的 for 循环 (索引遍历)

这是最基础、最直观的遍历方式,通过索引来访问每一个元素。

代码示例:

System.out.println("--- 方法一:传统的 for 循环 ---");
for (int i = 0; i < fruits.size(); i++) {
    String fruit = fruits.get(i);
    System.out.println(fruit);
}

优点:

  • 简单易懂:对于初学者来说非常直观。
  • 随机访问:可以在循环体内通过 i 的值轻松地访问任意位置的元素,非常适合需要根据索引进行操作的场景(需要知道当前是第几个元素)。

缺点:

ArrayList遍历有几种方式?各有什么区别?-图2
(图片来源网络,侵删)
  • 代码冗长:相比其他方法,需要写更多的代码。
  • 性能问题:对于非 RandomAccess 接口的实现类(如 LinkedList),每次调用 get(i) 都需要从头开始遍历,效率较低,虽然 ArrayListRandomAccess 的,性能很好,但这种写法不够通用。

增强 for 循环 (For-Each 循环)

这是 Java 5 引入的一种语法糖,专门用于遍历集合和数组,代码更简洁。

代码示例:

System.out.println("\n--- 方法二:增强 for 循环 (For-Each) ---");
for (String fruit : fruits) {
    System.out.println(fruit);
}

优点:

  • 代码简洁:代码非常干净,可读性高。
  • 不易出错:不需要关心索引的初始化、边界条件和递增,减少了出错的可能性。
  • 性能好:对于 ArrayList,其内部实现和传统的 for 循环性能几乎一样。

缺点:

  • 无法获取索引:无法在循环中获取当前元素的索引。
  • 无法修改集合:如果在遍历过程中尝试删除或添加元素(除了当前元素),会抛出 ConcurrentModificationException 异常,如果需要安全地删除元素,请参考方法五。

适用场景:

  • 最常用、最推荐的场景:当你只需要集合中的每一个元素,而不关心其索引时,这是首选方法。

使用 Iterator (迭代器)

Iterator 是专门为遍历集合而设计的接口,它提供了更安全的遍历方式,尤其是在需要修改集合时。

代码示例:

System.out.println("\n--- 方法三:使用 Iterator ---");
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
    String fruit = iterator.next();
    System.out.println(fruit);
}

优点:

  • 安全删除:是唯一可以在遍历过程中安全地删除元素的方法,必须使用 iterator.remove(),而不是 list.remove()
  • 通用性强:适用于所有集合类,无论其是否实现了 RandomAccess 接口。
  • 职责分离:将遍历和集合本身的操作分离开来。

缺点:

  • 代码稍显复杂:相比 For-Each 循环,代码量稍多。

适用场景:

  • 需要在遍历过程中删除或修改集合元素时。

使用 Java 8+ 的 forEach() 方法 (Lambda 表达式)

这是 Java 8 引入的函数式编程风格,非常现代和简洁。

代码示例:

System.out.println("\n--- 方法四:使用 Java 8+ 的 forEach() ---");
// 1. 使用 Lambda 表达式
fruits.forEach(fruit -> System.out.println(fruit));
// 2. 使用方法引用 (更简洁,如果只是调用一个方法)
fruits.forEach(System.out::println);

优点:

  • 非常简洁:一行代码即可完成遍历和操作。
  • 函数式风格:符合现代编程趋势,易于与其他 Stream API 操作结合。
  • 性能良好:内部实现和 For-Each 循环类似。

缺点:

  • 需要 Java 8+ 环境
  • 无法获取索引:和 For-Each 一样,无法直接获取索引。
  • 无法在遍历中修改集合:同样会抛出 ConcurrentModificationException

适用场景:

  • 现代 Java 项目中,对集合进行简单的、无副作用的操作(如打印、计算等)。

使用 Java 8+ 的 Stream API

Stream API 提供了强大的、声明式的方式来处理集合数据,遍历只是其众多功能中的一个。

代码示例:

System.out.println("\n--- 方法五:使用 Java 8+ 的 Stream API ---");
// 简单遍历
fruits.stream().forEach(System.out::println);
// 结合其他操作,例如过滤
System.out.println("\n--- Stream API 结合过滤 ---");
fruits.stream()
      .filter(fruit -> fruit.startsWith("A")) // 过滤出以 "A" 开头的元素
      .forEach(System.out::println);         // 打印过滤后的结果

优点:

  • 功能极其强大:可以方便地进行链式操作,如过滤、排序、映射、聚合等。
  • 并行处理:只需将 stream() 换成 parallelStream(),即可轻松实现并行计算,利用多核 CPU 提升性能。
  • 代码清晰:声明式代码,更易于表达业务逻辑。

缺点:

  • 性能开销:对于简单的遍历,创建 Stream 对象会带来一些额外的性能开销。
  • 需要 Java 8+ 环境
  • 无法在遍历中修改集合

适用场景:

  • 当你不仅需要遍历,还需要对集合进行复杂的转换、过滤、聚合等操作时,这是处理数据流的最佳实践。

如何安全地在遍历中删除元素?

这是一个经典问题,错误的做法会导致 ConcurrentModificationException

错误示范 (会抛异常):

for (String fruit : fruits) {
    if ("Banana".equals(fruit)) {
        fruits.remove(fruit); // 错误!
    }
}

正确做法 1:使用 Iterator

Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
    String fruit = iterator.next();
    if ("Banana".equals(fruit)) {
        iterator.remove(); // 正确!使用迭代器的 remove 方法
    }
}
// fruits 现在只包含 "Apple" 和 "Orange"

正确做法 2:使用 Java 8+ 的 removeIf() (推荐) 这是最简洁、最现代的方法。

fruits.removeIf(fruit -> "Orange".equals(fruit));
// fruits 现在只包含 "Apple"

总结与选择建议

方法 优点 缺点 最佳适用场景
传统 for 循环 直观,可获取索引 代码冗长,通用性差 需要操作索引或随机访问元素时。
增强 for 循环 简洁,可读性高,性能好 无法获取索引,遍历时不能修改集合 最常用,仅需遍历元素,不关心索引。
Iterator 安全删除,通用性强 代码稍复杂 需要在遍历过程中安全地删除元素时。
forEach() 极其简洁,函数式风格 无法获取索引,遍历时不能修改集合 Java 8+,对集合进行简单、无副作用的操作。
Stream API 功能强大,支持并行,声明式 简单遍历有开销,遍历时不能修改集合 需要对集合进行复杂的链式操作(过滤、排序等)。

一句话建议:

  • 如果只是遍历:优先使用 增强 for 循环Java 8+ 的 forEach()
  • 如果需要删除元素:使用 IteratorJava 8+ 的 removeIf()
  • 如果需要进行复杂的数据处理:使用 Stream API
分享:
扫描分享到社交APP
上一篇
下一篇