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 就能更优雅、更函数式了。