1. 简介

在本篇文章中,我们将探讨如何使用 Java Stream API 来操作 Map 数据结构。虽然有些场景下可以通过双向 Map(Bidirectional Map)来解决问题,但我们更关注的是使用函数式编程风格结合 Stream 的方式来处理。

首先会介绍如何将 Map 转换为 Stream 可处理的结构,然后通过几个实际问题展示具体用法。

2. 基本思路

核心点: Stream 是元素序列,而 Map 是键值对映射结构,并不具备顺序性。不过我们可以通过一些方法将 Map 转换为 Collection,从而获得 Stream 支持。

来看几种常见的转换方式:

Map<String, Integer> someMap = new HashMap<>();

获取 entry 集合:

Set<Map.Entry<String, Integer>> entries = someMap.entrySet();

获取 key 集合:

Set<String> keySet = someMap.keySet();

获取 value 集合:

Collection<Integer> values = someMap.values();

有了这些集合之后,就可以轻松地调用 .stream() 方法:

Stream<Map.Entry<String, Integer>> entriesStream = entries.stream();
Stream<Integer> valuesStream = values.stream();
Stream<String> keysStream = keySet.stream();

⚠️ 这些入口是我们在后续操作中处理 Map 的关键。

3. 使用 Stream 获取 Map 的 Key

3.1. 示例数据

假设我们有一个书籍信息的 Map,以 ISBN 作为 key,书名作为 value:

Map<String, String> books = new HashMap<>();
books.put("978-0201633610", "Design patterns : elements of reusable object-oriented software");
books.put("978-1617291999", "Java 8 in Action: Lambdas, Streams, and functional-style programming");
books.put("978-0134685991", "Effective Java");

我们的目标是找出书名为 “Effective Java” 的 ISBN。

3.2. 单个匹配查找

由于可能存在查不到的情况,我们使用 Optional 包装结果:

Optional<String> optionalIsbn = books.entrySet().stream()
  .filter(e -> "Effective Java".equals(e.getValue()))
  .map(Map.Entry::getKey)
  .findFirst();

assertEquals("978-0134685991", optionalIsbn.get());

✅ 分析这段代码逻辑:

  • 先获取 entrySet;
  • 使用 filter 过滤出值为 “Effective Java” 的条目;
  • 使用 map 提取 key(即 ISBN);
  • 最后用 findFirst 获取第一个匹配项并包装成 Optional。

再看一个查不到的例子:

Optional<String> optionalIsbn = books.entrySet().stream()
  .filter(e -> "Non Existent Title".equals(e.getValue()))
  .map(Map.Entry::getKey)
  .findFirst();

assertEquals(false, optionalIsbn.isPresent());

❌ 没有找到对应标题时,返回空 Optional。

3.3. 多个匹配结果

如果我们需要获取多个匹配的结果,比如所有书名以 “Effective Java” 开头的书籍:

先添加一本新书:

books.put("978-0321356680", "Effective Java: Second Edition");

然后进行查询:

List<String> isbnCodes = books.entrySet().stream()
  .filter(e -> e.getValue().startsWith("Effective Java"))
  .map(Map.Entry::getKey)
  .collect(Collectors.toList());

assertTrue(isbnCodes.contains("978-0321356680"));
assertTrue(isbnCodes.contains("978-0134685991"));

✅ 这次我们不再用 findFirst(),而是把所有符合条件的 key 收集到 List 中。

4. 使用 Stream 获取 Map 的 Value

这次换个角度:根据 key 查找 value

比如我们要找出所有以 “978-0” 开头的 ISBN 对应的书名:

List<String> titles = books.entrySet().stream()
  .filter(e -> e.getKey().startsWith("978-0"))
  .map(Map.Entry::getValue)
  .collect(Collectors.toList());

assertEquals(2, titles.size());
assertTrue(titles.contains("Design patterns : elements of reusable object-oriented software"));
assertTrue(titles.contains("Effective Java"));

✅ 同样是流式处理 entrySet,只不过这次是从 key 过滤、取 value。

如果只需要第一个匹配项,可以替换 collect(Collectors.toList())findFirst()

5. 总结

这篇文章展示了如何将 Map 转化为 Stream 并进行函数式处理。

✅ 核心技巧总结如下:

步骤 功能
entrySet().stream() 获取 Map 的 entry 流
filter() 按条件筛选 entry
map() 提取 key 或 value
findFirst() / collect() 获取单个或多个结果

💡 简单粗暴地说:Map 本身不能直接流式处理,但它的 entry/key/value 都可以!

熟练掌握这套组合拳,处理 Map 就能更优雅、更函数式了。


原始标题:Working With Maps Using Streams | Baeldung