在Java中,遍历Map(映射)有多种方式,尤其是在Java 8及以上版本引入了Stream API和Lambda表达式后,变得更加灵活和简洁,以下是几种常见的遍历Map的方法,从传统到现代,并附有详细说明和示例代码。
假设我们有以下一个Map作为示例:
import java.util.HashMap;
import java.util.Map;
public class MapIterationExample {
public static void main(String[] args) {
Map<String, Integer> studentScores = new HashMap<>();
studentScores.put("Alice", 95);
studentScores.put("Bob", 88);
studentScores.put("Charlie", 76);
studentScores.put("David", 99);
// 我们将用不同的方法遍历这个Map
}
}
使用 entrySet() 和 for-each 循环 (最常用、最推荐)
这是最传统也是最推荐的遍历方式,因为它同时获取键和值,效率高且代码清晰。
原理:
map.entrySet() 返回一个 Set<Map.Entry<K, V>>,其中每个 Map.Entry 对象代表一个键值对,然后通过for-each循环遍历这个 Set。
代码示例:
System.out.println("--- 方法一:使用 entrySet() 和 for-each 循环 ---");
for (Map.Entry<String, Integer> entry : studentScores.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("学生: " + key + ", 分数: " + value);
}
优点:
- 高效:直接获取键值对,避免了通过
key再去map中查找value的开销。 - 代码清晰:意图明确,易于阅读和维护。
- 兼容性好:适用于所有Java版本。
使用 keySet() 和 for-each 循环
如果你在遍历过程中只需要键,或者需要在循环中修改Map的值(但不能删除键),可以使用此方法。
原理:
map.keySet() 返回一个包含所有键的 Set,遍历这个 Set,然后通过 map.get(key) 来获取对应的值。
代码示例:
System.out.println("\n--- 方法二:使用 keySet() 和 for-each 循环 ---");
for (String key : studentScores.keySet()) {
Integer value = studentScores.get(key);
System.out.println("学生: " + key + ", 分数: " + value);
}
优点:
- 当你只需要键时,非常直接。
缺点:
- 效率较低:对于每个键,都需要调用一次
map.get(key)来获取值,这会多一次查找操作。 - 不能在遍历时删除元素:如果你在循环中调用
map.remove(key),可能会导致ConcurrentModificationException(并发修改异常),如果你确实需要删除,应该使用迭代器的remove()方法。
使用 values() 和 for-each 循环
如果你在遍历过程中只需要值,可以使用此方法。
原理:
map.values() 返回一个包含所有值的 Collection,直接遍历这个 Collection。
代码示例:
System.out.println("\n--- 方法三:使用 values() 和 for-each 循环 ---");
for (Integer value : studentScores.values()) {
System.out.println("分数: " + value);
}
优点:
- 当你只需要值时,非常简洁。
缺点:
- 无法获取键:如果你在循环中需要同时知道键和值,此方法不适用。
使用迭代器 (Iterator)
这是在Java 5之前(以及之后)处理集合时最安全的方式,特别是当你需要在遍历过程中删除元素时。
原理:
通过 iterator() 方法获取迭代器,然后使用 while 循环和 hasNext()/next() 来遍历。
代码示例:
System.out.println("\n--- 方法四:使用迭代器 (Iterator) ---");
Iterator<Map.Entry<String, Integer>> iterator = studentScores.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
System.out.println("学生: " + entry.getKey() + ", 分数: " + entry.getValue());
// 示例:在遍历时安全地删除一个元素
if ("Bob".equals(entry.getKey())) {
iterator.remove(); // 正确的删除方式
}
}
System.out.println("删除Bob后的Map: " + studentScores);
优点:
- 安全删除:是遍历时删除元素的标准且安全的方式,可以避免
ConcurrentModificationException。
缺点:
- 代码比for-each循环略显冗长。
使用 Java 8+ 的 forEach() 和 Lambda 表达式 (现代风格)
Java 8引入了Stream API和Lambda表达式,为集合遍历提供了更函数式、更简洁的语法。
原理:
Map 接口提供了一个 forEach(BiConsumer<? super K, ? super V> action) 方法,接受一个函数式接口 BiConsumer(二元消费者)作为参数,该接口接收两个参数(键和值)。
代码示例:
// 重置Map以便演示
studentScores.put("Bob", 88);
System.out.println("\n--- 方法五:使用 Java 8+ 的 forEach() 和 Lambda 表达式 ---");
studentScores.forEach((key, value) -> {
System.out.println("学生: " + key + ", 分数: " + value);
});
或者使用方法引用(如果只是简单地打印):
System.out.println("\n--- 方法五:使用 forEach() 和 方法引用 ---");
studentScores.forEach(MapIterationExample::printEntry);
// 定义一个静态方法来打印
public static void printEntry(String key, Integer value) {
System.out.println("学生: " + key + ", 分数: " + value);
}
优点:
- 代码简洁:一行代码即可完成遍历,非常优雅。
- 函数式风格:符合现代编程趋势。
缺点:
- 无法直接中断或删除:
forEach内部的操作是“fire-and-forget”式的,你不能使用break或continue,也不能在内部安全地删除元素(除非你使用外部标志和外部集合来记录要删除的项,然后在循环后删除),如果需要删除,迭代器仍然是更好的选择。
使用 Java 8+ 的 Stream API (高级用法)
Stream API提供了更强大的功能,如过滤、映射、聚合等,适用于复杂的遍历和数据处理场景。
原理:
将 Map 转换为 Stream,然后使用Stream的各种中间和终端操作。
代码示例:
System.out.println("\n--- 方法六:使用 Java 8+ 的 Stream API ---");
// 1. 遍历所有键值对
System.out.println("--- 遍历所有键值对 ---");
studentScores.entrySet().stream()
.forEach(entry -> System.out.println(entry));
// 2. 遍历并筛选分数大于90的学生
System.out.println("\n--- 筛选分数大于90的学生 ---");
studentScores.entrySet().stream()
.filter(entry -> entry.getValue() > 90)
.forEach(entry -> System.out.println(entry));
// 3. 遍历并只获取键
System.out.println("\n--- 只获取键 ---");
studentScores.keySet().stream()
.forEach(System.out::println);
// 4. 遍历并只获取值
System.out.println("\n--- 只获取值 ---");
studentScores.values().stream()
.forEach(System.out::println);
优点:
- 功能强大:可以进行链式操作,非常灵活,适合复杂的数据处理。
- 并行处理:可以轻松地切换为并行流
.parallelStream()来利用多核CPU提高性能。
缺点:
- 对于简单的遍历,可能有些“杀鸡用牛刀”,代码量比
forEach多。
总结与选择建议
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
entrySet() + for-each |
通用场景,需要同时访问键和值。 | 高效、清晰、兼容性好。 | - |
keySet() + for-each |
只需要键,或者需要修改值。 | 直接获取键。 | 效率较低,不能安全删除。 |
values() + for-each |
只需要值。 | 简洁。 | 无法获取键。 |
Iterator |
需要在遍历过程中安全地删除元素。 | 安全删除。 | 代码稍显冗长。 |
forEach() + Lambda |
现代风格,简洁的遍历,不需要修改集合。 | 代码极其简洁、优雅。 | 无法中断或安全删除。 |
Stream API |
需要进行复杂的链式操作(过滤、转换等)。 | 功能强大,支持并行。 | 对简单遍历可能过于复杂。 |
最终建议:
- 日常开发:优先选择 方法一 (
entrySet()+ for-each),它是最经典、最通用、最不容易出错的选择。 - Java 8+ 项目:如果只是简单地打印或处理,且不需要修改集合,方法五 (
forEach()+ Lambda) 是一个非常棒的选择,代码更现代。 - 需要删除元素:必须使用 方法四 (
Iterator)。 - 复杂数据处理:使用 方法六 (
Stream API)。
