杰瑞科技汇

Java HashSet遍历有几种常用方法?

HashSet 是 Java 集合框架中一个非常重要的实现,它基于 HashMap,有以下特点:

  • 不保证元素顺序:元素的存储顺序(遍历顺序)不等于插入顺序,也不等于任何固定的顺序。
  • 不允许重复元素:如果尝试添加一个已存在的元素,添加操作会失败,set 保持不变。
  • 允许 null 元素:可以添加一个 null 元素。
  • 非线程安全:在多线程环境下需要外部同步。

遍历 HashSet 主要有以下四种常用方法,我们将逐一介绍,并提供代码示例和优缺点分析。


准备工作:创建一个示例 HashSet

为了演示,我们先创建一个 HashSet 并添加一些元素。

import java.util.HashSet;
import java.util.Set;
public class HashSetIterationExample {
    public static void main(String[] args) {
        // 创建一个 HashSet 并添加一些字符串
        Set<String> fruits = new HashSet<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");
        fruits.add("Grape");
        fruits.add(null); // HashSet 允许存一个 null
        fruits.add("Apple"); // 重复元素,不会被添加
        System.out.println("原始 HashSet: " + fruits);
    }
}

使用增强 for 循环 (For-Each Loop)

这是最常用、最简洁、也最推荐的方式,它内部是使用迭代器实现的,但语法更简单。

语法:

for (String fruit : fruits) {
    // 处理 fruit
}

完整示例:

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

优点:

  • 代码简洁易读:语法非常清晰,是遍历集合的首选。
  • 避免手动管理迭代器:不容易出错。

缺点:

  • 不能在遍历过程中修改集合:如果在循环中调用 fruits.remove(fruit),会抛出 ConcurrentModificationException 异常,如果需要删除元素,请使用方法三或方法四。

使用迭代器 (Iterator)

这是最传统、最安全的方式,也是增强 for 循环的底层实现,它提供了在遍历过程中安全删除元素的能力。

语法:

Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
    String fruit = iterator.next();
    // 处理 fruit
}

完整示例:

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

优点:

  • 安全:可以在遍历过程中安全地删除元素。必须使用迭代器自己的 remove() 方法
  • 通用:所有 Collection 接口的实现都支持。

缺点:

  • 代码稍显冗长:相比增强 for 循环,需要多写几行代码。

重要:如何在遍历时安全删除元素?

System.out.println("\n--- 使用迭代器安全删除元素 ---");
// 删除 "Orange"
Iterator<String> removeIterator = fruits.iterator();
while (removeIterator.hasNext()) {
    String fruit = removeIterator.next();
    if ("Orange".equals(fruit)) {
        removeIterator.remove(); // 正确!使用迭代器的 remove 方法
    }
}
System.out.println("删除 'Orange' 后的 HashSet: " + fruits);

错误示范:

// for (String fruit : fruits) {
//     if ("Apple".equals(fruit)) {
//         fruits.remove(fruit); // 会抛出 ConcurrentModificationException
//     }
// }

使用 Java 8+ 的 forEach 和 Lambda 表达式

这是现代 Java 中非常流行和函数式的方式,代码非常优雅。

语法:

fruits.forEach(fruit -> {
    // 处理 fruit
});

完整示例:

System.out.println("\n--- 方法三:使用 forEach 和 Lambda ---");
fruits.forEach(fruit -> System.out.println(fruit));

如果操作很简单,甚至可以简化为方法引用:

System.out.println("\n--- 使用 forEach 和方法引用 ---");
fruits.forEach(System.out::println);

优点:

  • 代码简洁、现代:非常适合函数式编程风格。
  • 内部使用迭代器:底层也是迭代器,所以遍历时直接调用集合的 remove() 方法同样会抛出 ConcurrentModificationException,但它提供了另一种删除方式。

缺点:

  • 外部修改的限制:和增强 for 循环一样,不能直接在 Lambda 表达式中调用集合的 remove() 方法。

如何在遍历时安全删除(Lambda 方式)? 可以使用 removeIf 方法,这是专门为这个场景设计的。

System.out.println("\n--- 使用 removeIf 删除元素 ---");
// 删除所有以 "G" 开头的元素
fruits.removeIf(fruit -> fruit != null && fruit.startsWith("G"));
System.out.println("删除以 'G' 开头的元素后: " + fruits);

removeIf 方法内部会安全地使用迭代器,所以不会抛出异常。


使用并行流 (Parallel Stream)

对于非常大的 HashSet,可以利用多核 CPU 的优势进行并行遍历,以提高处理速度。

语法:

fruits.parallelStream().forEach(fruit -> {
    // 处理 fruit (注意:处理顺序是不确定的)
});

完整示例:

System.out.println("\n--- 方法四:使用并行流 ---");
// 注意:并行流的处理顺序是不确定的,并且是并发的
fruits.parallelStream().forEach(System.out::println);

优点:

  • 性能高:对于大数据集,可以显著提高处理速度。

缺点:

  • 顺序不确定:元素的遍历顺序完全无法预测,因为它们被分配到不同的线程中处理。
  • 线程安全forEach 内部的操作不是线程安全的,可能会导致数据竞争或不可预期的结果。
  • 不适合小数据集:对于少量数据,并行化的开销可能比带来的收益更大。

总结与对比

方法 语法 优点 缺点 适用场景
增强 for 循环 for (T item : set) 最简洁、最易读 遍历中不能修改集合 日常遍历,首选
迭代器 while (iterator.hasNext()) 最安全,可安全删除 代码稍显冗长 需要在遍历中删除或添加元素时
forEach + Lambda set.forEach(item -> ...) 现代、简洁,函数式风格 遍历中不能直接修改集合 现代Java开发,代码优雅
并行流 set.parallelStream().forEach(...) 性能高,利用多核 顺序不确定,有线程安全风险 处理大数据集且顺序不重要时

最佳实践建议

  1. 仅遍历,不修改:如果只是简单地读取集合中的每一个元素,优先使用增强 for 循环,它是最直观、最不容易出错的选择。
  2. 遍历中需要删除元素必须使用迭代器,或者调用集合的 removeIf 方法(Java 8+)。
  3. 追求代码的函数式风格:如果项目使用 Java 8 或更高版本,并且代码逻辑简单,使用 forEach + Lambda 表达式会让代码看起来更现代。
  4. 处理海量数据:当 HashSet 中的元素数量非常庞大,并且处理每个元素的开销也比较大时,可以考虑使用并行流来提升性能,但要特别注意其不确定性和线程安全问题。
分享:
扫描分享到社交APP
上一篇
下一篇