Is there a way to achieve the following example code, leveraging Java Stream API rather than having to create a HashMap
and populate it inside double forEach
es? I was trying to play around with groupingBy
and flatMap
but couldn’t find a way out.
Having a list of Movies, where each one has a list of genres (Strings)…
class Movie {
List<String> genres;
}
List<Movie> movies = new ArrayList<>();
…I want to group all the movies by genre
Map<String, List<Movie>> moviesByGenre = new HashMap();
movies.stream()
.forEach(movie -> movie.getGenres()
.stream()
.forEach(genre -> moviesByGenre
.computeIfAbsent(genre, k -> new ArrayList<>())
.add(movie)));
Advertisement
Answer
This one is tricky because you cannot define a key for each Movie
since such an object can appear under multiple keys.
The best solution is as far as I know equal to yours:
Map<String, List<Movie>> groupedMovies = new HashMap<>();
movies.forEach(movie -> {
movie.getGenres().forEach(genre ->
groupedMovies.computeIfAbsent(genre, g -> new ArrayList<>()).add(movie)
);
});
If you want to “convert” this snippet into java-stream, you have to start with what you have – which is the individual genres. Extract them from each Movie
using flatMap
and distinct
to avoid duplicates. Then use a Collector.toMap
to get the desired output.
- Key:
Function.identity()
to map each uniquegenre
as a key itself. - Value: Use another
Stream
to filter out the movies containing a particulargenre
to assign them to the key.
Map<String, List<Movie>> groupedMovies = movies.stream()
.map(Movie::getGenres)
.flatMap(List::stream)
.distinct()
.collect(Collectors.toMap(
Function.identity(),
genre -> movies.stream()
.filter(movie -> movie.getGenres().contains(genre))
.collect(Collectors.toList())));
The procedural approach in the first snippet is faster, easier to read and understand. I don’t recommend using java-stream here.
A note.. there is no meaning of using forEach
right after stream
: The sequence of list.stream().forEach(...)
can be list.forEach(...)
instead.