I have a data structure named "Book" that consists of the following fields:
public final class Book {
private final String title;
private final BookType bookType;
private final List<Author> authors;
}
My goal is to derive a Map<Author, List<BookType>>
from a List<Book>
using Stream API.
To achieve it, at first, I've made a for-each loop to clarify the steps of the solution and after I've rewritten it into streams based approach step-by-step:
Map<Author, List<BookType>> authorListBookType = new HashMap<>();
books.stream().forEach(b -> b.getAuthors().stream().forEach(e -> {
if (authorListBookType.containsKey(e)) {
authorListBookType.get(e).add(b.getBookType());
} else {
authorListBookType.put(e, new ArrayList<>(Collections.singletonList(b.getBookType())));
}
}));
But it isn't a Stream API based solution and I've gotten in stuck and I don't know how to finish it properly.
I know that I must use grouping collector to obtain the required Map<Author, List<BookType>>
straight from streams.
Could you give me some hints, please?
You should pair each author of each book with its book type, then collect:
Map<Author, Set<BookType>> authorListBookType = books.stream()
.flatMap(book -> book.getAuthors().stream()
.map(author -> Map.entry(author, book.getType())))
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.mapping(
Map.Entry::getValue,
Collectors.toSet())));
Here I've used Java 9's Map.entry(key, value)
to create the pairs, but you can use new AbstractMap.SimpleEntry<>(key, value)
or any other Pair
class at your disposal.
This solution uses Collectors.groupingBy
and Collectors.mapping
to create the desired Map
instance.
As @Bohemian points out in the comments, you need to collect to a Set
instead of a List
to avoid duplicates.
However, I find the stream-based solution a little bit convoluted, because when you pair authors and book types in Map.Entry
instances, you then have to use Map.Entry
methods in the Collectors.groupingBy
part, thus losing the initial semantics of your solution, as well as some readability...
So here's another solution:
Map<Author, Set<BookType>> authorListBookType = new HashMap<>();
books.forEach(book ->
book.getAuthors().forEach(author ->
authorListBookType.computeIfAbsent(author, k -> new HashSet<>())
.add(book.getType())));
Both solutions assume Author
implements hashCode
and equals
consistently.