HashSet 是 Java 集合框架中一个常用的实现类,它基于 HashMap 实现,不保证元素的顺序,并且不允许有重复的元素,遍历 HashSet 有多种方式,下面我将从最常用到其他方式进行详细介绍,并提供代码示例。

准备工作:创建一个示例 HashSet
我们创建一个 HashSet 并添加一些元素,用于后续的遍历演示。
import java.util.HashSet;
import java.util.Set;
public class HashSetTraversalExample {
public static void main(String[] args) {
// 创建一个 HashSet 并添加一些字符串
Set<String> fruits = new HashSet<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
fruits.add("Grape");
// 重复添加 "Apple",它只会被存储一次
fruits.add("Apple");
System.out.println("原始 HashSet 内容: " + fruits);
// 注意:输出顺序可能与添加顺序不同,这是 HashSet 的特性
}
}
使用增强 for 循环 (For-Each Loop) - 最推荐
这是最常用、最简洁、最安全(避免并发修改异常)的遍历方式,它适用于所有实现了 Iterable 接口的集合,包括 HashSet。
语法:
for (Type element : set) {
// 对 element 进行操作
}
代码示例:

System.out.println("\n--- 使用增强 for 循环遍历 ---");
for (String fruit : fruits) {
System.out.println(fruit);
}
优点:
- 代码简洁易读:语法非常清晰,是 Java 遍历集合的首选方式。
- 安全:在单线程环境下,它不会抛出
ConcurrentModificationException(并发修改异常)。 - 高效:底层使用迭代器,性能良好。
缺点:
- 无法获取索引:如果你需要在遍历时获取元素的索引位置,这种方式不适用。
- 不能在遍历过程中安全地删除元素:虽然可以删除,但不符合其设计初衷,且在特定情况下(如使用
Iterator的remove方法时)需要特别注意。
使用迭代器 (Iterator)
迭代器是 Java 集合框架中用于遍历元素的“标准”方式,它提供了更强大的功能,尤其是在需要删除元素时。
语法:

Iterator<Type> iterator = set.iterator();
while (iterator.hasNext()) {
Type element = iterator.next();
// 对 element 进行操作
}
代码示例:
System.out.println("\n--- 使用迭代器 (Iterator) 遍历 ---");
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
}
优点:
- 功能强大:可以在遍历过程中安全地删除元素,这是它与
for-each循环最大的区别。 - 通用性:所有集合都支持迭代器。
如何安全地删除元素?
必须使用迭代器自己的 remove() 方法,而不是集合的 remove() 方法。
System.out.println("\n--- 使用迭代器安全地删除元素 ---");
// 假设我们要删除 "Orange"
Iterator<String> removeIterator = fruits.iterator();
while (removeIterator.hasNext()) {
String fruit = removeIterator.next();
if ("Orange".equals(fruit)) {
removeIterator.remove(); // 正确的删除方式
}
}
System.out.println("删除 'Orange' 后的 HashSet: " + fruits);
缺点:
- 代码稍显冗长:相比
for-each循环,需要多写几行代码。
使用 Java 8+ 的 forEach 和 Lambda 表达式
如果你使用的是 Java 8 或更高版本,可以利用 Lambda 表达式和 forEach 方法进行更函数式的遍历。
语法:
set.forEach(element -> {
// 对 element 进行操作
});
代码示例:
System.out.println("\n--- 使用 Java 8 forEach 和 Lambda 遍历 ---");
fruits.forEach(fruit -> System.out.println(fruit));
优点:
- 非常简洁和现代:代码量少,富有表现力。
- 函数式编程风格:符合现代编程趋势,易于与 Stream API 等结合。
缺点:
- Java 版本限制:需要 Java 8 或更高版本。
- 删除元素:
forEach内部也使用了迭代器,如果你想删除元素,仍然需要自己获取迭代器并调用remove(),它本身不提供直接删除当前元素的方法。
使用 Stream API (Java 8+)
Stream API 提供了一种声明式的方式来处理集合数据,遍历只是其众多功能之一,它更强调的是对数据的“处理”(如过滤、映射、聚合等)。
语法:
set.stream().forEach(element -> {
// 对 element 进行操作
});
代码示例:
System.out.println("\n--- 使用 Stream API 遍历 ---");
fruits.stream().forEach(fruit -> System.out.println(fruit));
优点:
- 极其强大和灵活:不仅仅是遍历,可以进行复杂的链式操作。
- 支持并行处理:只需将
stream()换成parallelStream(),就可以轻松实现并行计算,提高大数据量下的处理效率。
示例:使用 Stream 进行过滤和遍历
System.out.println("\n--- 使用 Stream API 过滤并遍历 ---");
// 只打印长度大于 5 的水果
fruits.stream()
.filter(fruit -> fruit.length() > 5)
.forEach(fruit -> System.out.println("长度大于5的水果: " + fruit));
缺点:
- 对于简单的遍历来说可能“杀鸡用牛刀”:如果只是想简单地打印所有元素,使用
for-each或forEach更直接。 - 性能开销:创建 Stream 对象本身会有轻微的性能开销,但对于大多数应用场景来说可以忽略不计。
使用传统的 for 循环和索引 - 不推荐用于 HashSet
理论上,你可以先将 HashSet 转换为数组,然后用传统的 for 循环遍历数组。
代码示例:
System.out.println("\n--- (不推荐) 转换为数组后使用传统 for 循环遍历 ---");
Object[] fruitArray = fruits.toArray();
for (int i = 0; i < fruitArray.length; i++) {
System.out.println(fruitArray[i]);
}
为什么不推荐?
- 效率低:
toArray()方法会创建一个新的数组对象,增加了内存分配和复制的开销。 - 代码冗余:相比其他方法,这种方式代码更长,可读性更差。
- 违背了
Set的设计初衷:Set是一个无序的集合,将其转换为有序的数组来遍历,在概念上就不太合适。
总结与选择建议
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 增强 for 循环 | 代码简洁、易读、安全 | 无法获取索引,删除元素不便 | 绝大多数遍历场景的首选 |
| 迭代器 | 可以安全地删除元素 | 代码稍显冗长 | 需要在遍历过程中修改集合(删除元素)时 |
| Java 8 forEach | 代码简洁、现代、函数式 | Java 8+,删除元素需另寻他法 | Java 8+ 环境,喜欢函数式编程风格 |
| Stream API | 功能强大、灵活、支持并行 | 对简单遍历可能“小题大做” | 需要对集合进行复杂处理(过滤、映射等)或并行计算 |
| 传统 for 循环 | 可以获取索引 | 效率低、代码冗余、不推荐 | 几乎不推荐用于 HashSet |
- 只想遍历,不做任何修改:用 增强 for 循环。
- 遍历过程中要删除元素:用 迭代器。
- 使用 Java 8+,想写点新潮的代码:用
forEach或stream().forEach()。 - 需要对数据进行复杂的链式操作:用 Stream API。
