使用Stream来处理Map
1.绪论
在本教程中,我们将讨论如何使用 Java Stream 的一些示例使用 Map。值得注意的是,其中一些练习可以使用双向 Map 数据结构来解决,但我们在这里对函数式方法感兴趣。
首先,我们将解释我们将使用Maps和Stream工作的基本想法。然后,我们将介绍几个与Map有关的不同问题,以及使用Stream的具体解决方案。
进一步的阅读。
2.基本理念
主要需要注意的是,Stream是元素的序列,可以很容易地从Collection中获得。
Map有一个不同的结构,有一个从键到值的映射,没有序列。然而,这并不意味着我们不能将Map结构转换为不同的序列,然后让我们以自然的方式与Stream API一起工作。
让我们看看如何从一个Map中获得不同的Collection,然后我们可以把这些Collection转变成一个Stream。
Map<String, Integer> someMap = new HashMap<>();
我们可以得到一组键-值对。
Set<Map.Entry<String, Integer>> entries = someMap.entrySet();
我们还可以获得与Map相关的键集。
Set<String> keySet = someMap.keySet();
或者,我们可以直接用值集合来工作。
Collection<Integer> values = someMap.values();
这些都为我们提供了一个入口,通过从这些集合中获取流来处理这些集合。
Stream<Map.Entry<String, Integer>> entriesStream = entries.stream();
Stream<Integer> valuesStream = values.stream();
Stream<String> keysStream = keySet.stream();
3.使用Stream获得Map的键
3.1.输入数据
让我们假设我们有一个Map:
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.”的书的国际标准书号。
3.2.检索匹配信息
由于书名不可能存在于我们的Map中,我们希望能够表明没有相关的ISBN。我们可以用Optional来表达。
让我们假设在这个例子中,我们对与该书名相匹配的书籍的任何键感兴趣。
Optional<String> optionalIsbn = books.entrySet().stream()
.filter(e -> "Effective Java".equals(e.getValue()))
.map(Map.Entry::getKey)
.findFirst();
assertEquals("978-0134685991", optionalIsbn.get());
让我们分析一下这段代码。首先,我们从Map获得entrySet,正如我们之前看到的。
我们只想考虑以“Effective Java”为标题的条目,所以第一个中间操作将是一个过滤器。
我们对整个Map条目不感兴趣,而是对每个条目的键感兴趣。所以下一个链式中间操作就是这样做的:它是一个map操作,将生成一个新的流作为输出,其中只包含与我们所寻找的标题相匹配的条目的键。
由于我们只想要一个结果,我们可以应用findFirst()终端操作,这将提供Stream中的初始值作为一个Optional对象。
让我们来看看一个标题不存在的案例。
Optional<String> optionalIsbn = books.entrySet().stream()
.filter(e -> "Non Existent Title".equals(e.getValue()))
.map(Map.Entry::getKey).findFirst();
assertEquals(false, optionalIsbn.isPresent());
3.3.检索多个结果
现在让我们改变问题,看看我们如何处理返回多个结果而不是一个结果的问题。
为了有多个结果返回,让我们在我们的Map中添加以下书籍。
books.put("978-0321356680", "Effective Java: Second Edition");
因此,现在如果我们寻找所有以“Effective Java,”开头的书籍,我们会得到不止一个结果。
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"));
在这种情况下,我们所做的是替换过滤条件,以验证Map中的值是否以“Effective Java”开头,而不是比较String是否相等。
这一次我们收集结果,而不是只挑选第一个,并将匹配的结果放入一个List。
4.使用Streams获得Map的值
现在我们来关注一个不同的Map问题。不要根据书名获得ISBN,我们要尝试根据ISBN获得书名。
让我们使用原来的Map。我们想找到以“978-0”开头的书目。
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"));
这个解决方案与我们前一组问题的解决方案类似;我们将条目集流化,然后进行过滤、映射和收集。
也和以前一样,如果我们想只返回第一个匹配,那么在map方法之后,我们可以调用findFirst()方法,而不是在List中收集所有的结果。
5.总结
在这篇文章中,我们已经演示了如何以一种功能性的方式处理Map。
特别是,我们已经看到,一旦我们切换到使用相关的集合来映射,使用Stream的处理就会变得更加简单和直观。
当然,本文中所有的例子都可以在GitHub项目中找到。