JDK 8 Streams and Grouping
I wrote about the powerful features of using JDK 8‘s Streams with Java collections in the post Stream-Powered Collections Functionality in JDK 8. I did not cover use of the groupingBy Collector reduction operation in that post and so address grouping in this post.
The examples in this post will demonstrate how to combine Collection-backed Streams with groupingBy
Collectors to reorganize the underlying Collection‘s data in groups prescribed by a provided classification. These examples are based on the Movie
class and Set
of Movie
classes described in my earlier post Stream-Powered Collections Functionality in JDK 8.
The next code listing demonstrates how a simple statement can be used to group the provided Set
of Movie
s into a Map
of movie ratings (key) to movies with that rating (value). The groupingBy
Collector provides this Map
as a map of key type (the MpaaRating
in this case) to a List
of the type of objects being grouped (Movie
in this case).
/** * Demonstrate use of JDK 8 streams and Collectors.groupingBy to * group movies by their MPAA ratings. */ private static void demonstrateGroupingByRating() { final Map<MpaaRating, List<Movie>> moviesByRating = movies.stream().collect(groupingBy(Movie::getMpaaRating)); out.println("Movies grouped by MPAA Rating: " + moviesByRating); }
In the example just shown (and in the examples that follow in this post), statically importing java.util.stream.Collectors.groupingBy
allows me to NOT need to scope groupingBy
calls with the Collectors
class name. This simple code snippet groups the movies by their ratings with the returned Map
mapping key of movie rating to List
s of movies associated with each rating. Here is an example of the output when the provided Movie
set is the same as in my previously referenced post.
Movies grouped by MPAA Rating: {PG13=[Movie: Inception (2010), SCIENCE_FICTION, PG13, 13], R=[Movie: The Shawshank Redemption (1994), DRAMA, R, 1], PG=[Movie: Raiders of the Lost Ark (1981), ACTION, PG, 31, Movie: Back to the Future (1985), SCIENCE_FICTION, PG, 49, Movie: Star Wars: Episode V – The Empire Strikes Back (1980), SCIENCE_FICTION, PG, 12]}
A specific use of the capability just demonstrated is to generate a Map
of unique keys to objects in a Collection
to the object of that Collection
with that key. This might be useful, for example, when needing to look up objects repeatedly and quickly via map but being provided with the objects of interest in a Set
or List
instead of a Map
. Pretending for the moment that movies have unique titles (they only do for my small set), such functionality can be accomplished as shown in the next code listing.
/** * Demonstrate use of JDK 8 streams and Collectors.groupingBy to * group movies by their title. */ private static void demonstrateGroupingByTitle() { final Map<String, List<Movie>> moviesByTitle = movies.stream().collect(groupingBy(Movie::getTitle)); out.println("Movies grouped by title: " + moviesByTitle); }
Assuming that title is unique for each movie in the original collection, the code above provides a map of movie title to single-element List
containing only the movie for which that title is applicable. Any client wanting to quickly look up a movie by its title could call moviesByTitle.get(String).get(0)
to get the full Movie
object corresponding to that title. The output of doing this with my simple movie set is shown next.
Movies grouped by title: {The Shawshank Redemption=[Movie: The Shawshank Redemption (1994), DRAMA, R, 1], Star Wars: Episode V – The Empire Strikes Back=[Movie: Star Wars: Episode V – The Empire Strikes Back (1980), SCIENCE_FICTION, PG, 12], Back to the Future=[Movie: Back to the Future (1985), SCIENCE_FICTION, PG, 49], Raiders of the Lost Ark=[Movie: Raiders of the Lost Ark (1981), ACTION, PG, 31], Inception=[Movie: Inception (2010), SCIENCE_FICTION, PG13, 13]}
It is possible to group by two different characteristics. This allows for a Collection
to be grouped by one characteristic and then have each of those groups sub-grouped by a second characteristic. For example, the following code listing groups movies by rating and then by genre.
/** * Demonstrate use of JDK 8 streams and cascaded groupingBy * to group movies by ratings and then by genres within ratings. */ private static void demonstrateGroupingByRatingAndGenre() { final Map<MpaaRating, Map<Genre, List<Movie>>> moviesByRatingAndGenre = movies.stream().collect(groupingBy(Movie::getMpaaRating, groupingBy(Movie::getGenre))); out.println("Movies by rating and genre: " + moviesByRatingAndGenre); }
The code listing just shown first groups the underlying movies by rating and then groups each movie with a particular group of ratings again, but this time by genre. In other words, we get double-level groups of movies by ratings and genre. Output on my simple set of movies is shown next.
Movies by rating and genre: {PG13={SCIENCE_FICTION=[Movie: Inception (2010), SCIENCE_FICTION, PG13, 13]}, R={DRAMA=[Movie: The Shawshank Redemption (1994), DRAMA, R, 1]}, PG={SCIENCE_FICTION=[Movie: Back to the Future (1985), SCIENCE_FICTION, PG, 49, Movie: Star Wars: Episode V – The Empire Strikes Back (1980), SCIENCE_FICTION, PG, 12], ACTION=[Movie: Raiders of the Lost Ark (1981), ACTION, PG, 31]}}
The groupingBy
collector makes it easy to group elements of a List
or Set
into a map with the grouping characteristic as the key and the objects belonging to each group in a List
associated with that grouping characteristic key. This allows one all the advantages of a Map
, including use of some of the handy methods on Map
that have been introduced with JDK 8.
Reference: | JDK 8 Streams and Grouping from our JCG partner Dustin Marx at the Inspired by Actual Events blog. |