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:

Custom Cascading in Spring Data MongoDB
Java Program to Find Location of a Point Placed in Three Dimensions Using K-D Trees
Java Program to Show the Duality Transformation of Line and Point
How to Read HTTP Headers in Spring REST Controllers
Java Program to Perform Finite State Automaton based Search
Introduction to Spring Data JDBC
Spring Boot Tutorial – Bootstrap a Simple Application
Configuring a DataSource Programmatically in Spring Boot
How to Remove the Last Character of a String?
Java Program to Generate All Possible Combinations of a Given List of Numbers
Spring’s RequestBody and ResponseBody Annotations
Custom HTTP Header with the HttpClient
Java Program to Implement the Edmond’s Algorithm for Maximum Cardinality Matching
Lập trình hướng đối tượng (OOPs) trong java
Spring Boot - Rest Template
Cài đặt và sử dụng Swagger UI
Java Program to Perform Sorting Using B-Tree
Java Program to Check for balanced parenthesis by using Stacks
Spring REST API with Protocol Buffers
Java Program to Implement Quick sort
Entity To DTO Conversion for a Spring REST API
Registration – Activate a New Account by Email
Compact Strings in Java 9
Inheritance with Jackson
Auditing with JPA, Hibernate, and Spring Data JPA
Tránh lỗi NullPointerException trong Java như thế nào?
Java Program to Implement the Alexander Bogomolny’s UnOrdered Permutation Algorithm for Elements Fro...
Remove HTML tags from a file to extract only the TEXT
Performance Difference Between save() and saveAll() in Spring Data
DynamoDB in a Spring Boot Application Using Spring Data
Tạo ứng dụng Java RESTful Client không sử dụng 3rd party libraries
Sending Emails with Java