Working With Maps Using Streams – 使用流的地图工作

最后修改: 2019年 2月 14日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’ll discuss some examples of how to use Java Streams to work with Maps. It’s worth noting that some of these exercises could be solved using a bidirectional Map data structure, but we’re interested here in a functional approach.

在本教程中,我们将讨论一些关于如何使用Java Streams来处理Maps的例子。值得注意的是,其中一些练习可以用双向的Map数据结构来解决,但我们在这里感兴趣的是一种函数式的方法。

First, we’ll explain the basic idea we’ll be using to work with Maps and Streams. Then we’ll present a couple of different problems related to Maps and their concrete solutions using Streams.

首先,我们将解释我们使用MapsStreams工作的基本思路。然后我们将介绍几个与Maps相关的不同问题,以及使用Streams的具体解决方案。

2. Basic Idea

2.基本理念

The principal thing to notice is that Streams are sequences of elements which can be easily obtained from a Collection.

主要需要注意的是,Streams是元素的序列,可以很容易地从Collection获得。

Maps have a different structure, with a mapping from keys to values, without sequence. However, this doesn’t mean that we can’t convert a Map structure into different sequences which then allow us to work in a natural way with the Stream API.

Maps有一个不同的结构,有一个从键到值的映射,没有序列。然而,这并不意味着我们不能将Map结构转换为不同的序列,然后让我们以自然的方式与Stream API一起工作。

Let’s see ways of obtaining different Collections from a Map, which we can then pivot into a Stream:

让我们看看如何从Map中获得不同的Collections,然后我们可以将其转变成一个Stream

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

We can obtain a set of key-value pairs:

我们可以得到一组键-值对。

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

We can also get the key set associated with the Map:

我们还可以获得与Map相关的键集。

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

Or we could work directly with the set of values:

或者我们可以直接用价值集合工作。

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

These each give us an entry point to process those collections by obtaining streams from them:

这些都给了我们一个入口,通过从这些集合中获取流来处理这些集合。

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

3. Getting a Map‘s Keys Using Streams

3.使用Streams获取Map的密钥

3.1. Input Data

3.1.输入数据

Let’s assume we have a Map:

让我们假设我们有一个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");

We are interested in finding the ISBN for the book titled “Effective Java.”

我们有兴趣找到题为 “有效的Java “的书的ISBN。

3.2. Retrieving a Match

3.2.检索匹配信息

Since the book title could not exist in our Map, we want to be able to indicate that there is no associated ISBN for it. We can use an Optional to express that:

由于书名不可能存在于我们的Map中,我们希望能够表明它没有相关的ISBN。我们可以使用Optional 来表达。

Let’s assume for this example that we are interested in any key for a book matching that title:

让我们假设在这个例子中,我们对与该书名相匹配的书籍的任何钥匙感兴趣。

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

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

Let’s analyze the code. First, we obtain the entrySet from the Map, as we saw previously.

让我们分析一下这段代码。首先,我们从Map中获得entrySet,正如我们之前看到的。

We only want to consider the entries with “Effective Java” as the title, so the first intermediate operation will be a filter.

我们只想考虑以 “Effective Java “为标题的条目,所以第一个中间操作将是一个过滤器

We’re not interested in the whole Map entry, but in the key of each entry. So the next chained intermediate operation does just that: it is a map operation that will generate a new stream as output, which will contain only the keys for the entries that matched the title we were looking for.

我们对整个Map条目不感兴趣,而是对每个条目的键感兴趣。因此,下一个链式中间操作就是这样做的:它是一个map操作,将生成一个新的流作为输出,其中只包含与我们所寻找的标题匹配的条目的键。

As we only want one result, we can apply the findFirst() terminal operation, which will provide the initial value in the Stream as an Optional object.

由于我们只想要一个结果,我们可以应用findFirst()终端操作,这将在Stream中提供初始值作为Optional对象。

Let’s see a case in which a title does not exist:

让我们看看一个标题不存在的案例。

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. Retrieving Multiple Results

3.3.检索多个结果

Now let’s change the problem to see how we could deal with returning multiple results instead of one.

现在让我们改变问题,看看我们如何处理返回多个结果而不是一个。

To have multiple results returned, let’s add the following book to our Map:

为了有多个结果返回,让我们在我们的Map中添加以下书籍。

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

So now if we look for all books that start with “Effective Java,” we’ll get more than one result back:

因此,现在如果我们寻找所有以 “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"));

What we have done in this case is to replace the filter condition to verify if the value in the Map starts with “Effective Java” instead of comparing for String equality.

在这种情况下,我们所做的是替换过滤条件,以验证Map中的值是否以 “Effective Java “开始,而不是比较String是否相等。

This time we collect the results, instead of just picking the first, and put the matches into a List.

这一次,我们收集结果,而不是只挑选第一个,并将匹配的结果放入一个List

4. Getting a Map‘s Values Using Streams

4.使用Streams获取Map的值

Now let’s focus on a different problem with maps. Instead of obtaining ISBNs based on the titles, we’ll try and get titles based on the ISBNs.

现在让我们来关注一下地图的另一个问题。与其根据标题获得ISBNs,我们不如试着根据ISBNs获得标题

Let’s use the original Map. We want to find titles with an ISBN starting with “978-0”.

让我们使用原来的Map。我们想找到ISBN以 “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"));

This solution is similar to the solutions of our previous set of problems; we stream the entry set, and then filter, map, and collect.

这个解决方案与我们之前的问题集的解决方案类似;我们对条目集进行流式处理,然后进行过滤、映射和收集。

Also like before, if we wanted to return only the first match, then after the map method we could call the findFirst() method instead of collecting all the results in a List.

也和以前一样,如果我们想只返回第一个匹配,那么在map方法之后,我们可以调用findFirst()方法,而不是在List中收集所有结果。

5. Conclusion

5.总结

In this article, we’ve demonstrated how to process a Map in a functional way.

在这篇文章中,我们已经演示了如何以一种功能性的方式处理Map

In particular, we have seen that once we switch to using the associated collections to Maps, processing using Streams becomes much easier and intuitive.

特别是,我们已经看到,一旦我们将相关的集合转换为Maps,使用Streams的处理就会变得更加简单和直观。

Of course, all of the examples in this article can be found in the GitHub project.

当然,本文中所有的例子都可以在GitHub项目中找到。