Working With Maps Using Streams

1. Introduction

In this tutorial, we’ll discuss some examples of how to use Java Streamsto 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.

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.

2. Basic Idea

The principal thing to notice is that Streams are sequences of elements which can be easily obtained from a 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.

Let’s see ways of obtaining different Collections from a Map, which we can then pivot into a 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:

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.1. Input Data

Let’s assume we have a 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.”

3.2. Retrieving a Match

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:

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.

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

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.

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.

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

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:

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:

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.

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

4. Getting a Map‘s Values Using Streams

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.

Let’s use the original Map. We want to find titles with an ISBN starting with “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.

5. Conclusion

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

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

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

Related posts:

Most commonly used String methods in Java
Introduction to Spring Cloud Netflix – Eureka
Arrays.asList vs new ArrayList(Arrays.asList())
Spring Security OAuth2 – Simple Token Revocation
Creating a Generic Array in Java
Java Web Services – JAX-WS – SOAP
Spring Boot - Cloud Configuration Client
Sorting Query Results with Spring Data
Java Program to Check Whether an Undirected Graph Contains a Eulerian Cycle
Java Program to Implement Floyd-Warshall Algorithm
Spring Data – CrudRepository save() Method
Java Program to Implement Fisher-Yates Algorithm for Array Shuffling
Hướng dẫn Java Design Pattern – Observer
wait() and notify() Methods in Java
Beans and Dependency Injection
Java Program to do a Breadth First Search/Traversal on a graph non-recursively
Jackson Annotation Examples
Hướng dẫn sử dụng biểu thức chính quy (Regular Expression) trong Java
Java Program to Implement Shoelace Algorithm
StringBuilder vs StringBuffer in Java
Spring Boot - Creating Docker Image
Setting a Request Timeout for a Spring REST API
An Introduction to Java.util.Hashtable Class
Introduction to Thread Pools in Java
Spring Cloud AWS – S3
Initialize a HashMap in Java
Toán tử trong java
Java Program to Implement the Checksum Method for Small String Messages and Detect
Java Program to Solve the Fractional Knapsack Problem
Java Program to Implement the Schonhage-Strassen Algorithm for Multiplication of Two Numbers
Tránh lỗi ConcurrentModificationException trong Java như thế nào?
Java Program to Perform Quick Sort on Large Number of Elements