Using JDK 8 Streams to Convert Between Collections of Wrapped Objects and Collections of Wrapper Objects
I have found Decorators and Adapters to be useful from time to time as I have worked with Java-based applications. These “wrappers” work well in a variety of situations and are fairly easy to understand and implement, but things can become a bit more tricky when a hierarchy of objects rather than a single object needs to be wrapped. In this blog post, I look at how Java 8 streams make it easier to convert between collections of objects and collections of objects that wrap those objects.
For this discussion, I’ll apply two simple Java classes representing a Movie
class and a class that “wraps” that class called MovieWrapper
. The Movie
class was used in my post on JDK 8 enhancements to Java collections. The Movie
class and the class that wraps it are shown next.
Movie.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | package dustin.examples.jdk8.streams; import java.util.Objects; /** * Basic characteristics of a motion picture. * * @author Dustin */ public class Movie { /** Title of movie. */ private final String title; /** Year of movie's release. */ private final int yearReleased; /** Movie genre. */ private final Genre genre; /** MPAA Rating. */ private final MpaaRating mpaaRating; /** imdb.com Rating. */ private final int imdbTopRating; public Movie( final String newTitle, final int newYearReleased, final Genre newGenre, final MpaaRating newMpaaRating, final int newImdbTopRating) { this .title = newTitle; this .yearReleased = newYearReleased; this .genre = newGenre; this .mpaaRating = newMpaaRating; this .imdbTopRating = newImdbTopRating; } public String getTitle() { return this .title; } public int getYearReleased() { return this .yearReleased; } public Genre getGenre() { return this .genre; } public MpaaRating getMpaaRating() { return this .mpaaRating; } public int getImdbTopRating() { return this .imdbTopRating; } @Override public boolean equals(Object other) { if (!(other instanceof Movie)) { return false ; } final Movie otherMovie = (Movie) other; return Objects.equals( this .title, otherMovie.title) && Objects.equals( this .yearReleased, otherMovie.yearReleased) && Objects.equals( this .genre, otherMovie.genre) && Objects.equals( this .mpaaRating, otherMovie.mpaaRating) && Objects.equals( this .imdbTopRating, otherMovie.imdbTopRating); } @Override public int hashCode() { return Objects.hash( this .title, this .yearReleased, this .genre, this .mpaaRating, this .imdbTopRating); } @Override public String toString() { return "Movie: " + this .title + " (" + this .yearReleased + "), " + this .genre + ", " + this .mpaaRating + ", " + this .imdbTopRating; } } |
MovieWrapper.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package dustin.examples.jdk8.streams; /** * Wraps a movie like a Decorator or Adapter might. * * @author Dustin */ public class MovieWrapper { private Movie wrappedMovie; public MovieWrapper( final Movie newMovie) { this .wrappedMovie = newMovie; } public Movie getWrappedMovie() { return this .wrappedMovie; } public void setWrappedMovie( final Movie newMovie) { this .wrappedMovie = newMovie; } public String getTitle() { return this .wrappedMovie.getTitle(); } public int getYearReleased() { return this .wrappedMovie.getYearReleased(); } public Genre getGenre() { return this .wrappedMovie.getGenre(); } public MpaaRating getMpaaRating() { return this .wrappedMovie.getMpaaRating(); } public int getImdbTopRating() { return this .wrappedMovie.getImdbTopRating(); } @Override public String toString() { return this .wrappedMovie.toString(); } } |
With the Movie
and MovieWrapper
classes defined above, I now look at converting a collection of one of these into a collection of the other. Before JDK 8, a typical approach to convert a collection of Movie
objects into a collection of MovieWrapper
objects would to iterate over the source collection of Movie
objects and add each one to a new collection of MovieWrapper
objects. This is demonstrated in the next code listing.
Converting Collection of Wrapped Object Into Collection of Wrapper Objects
1 2 3 4 5 6 | // movies previously defined as Set<Movie> final Set<MovieWrapper> wrappedMovies1 = new HashSet<>(); for ( final Movie movie : movies) { wrappedMovies1.add( new MovieWrapper(movie)); } |
With JDK 8 streams, the operation above can now be implemented as shown in the next code listing.
Converting Collection of Wrapped Objects Into Collection of Wrapper Objects – JDK 8
1 2 3 | // movies previously defined as Set<Movie> final Set<MovieWrapper> wrappedMovies2 = movies.stream().map(movie -> new MovieWrapper(movie)).collect(Collectors.toSet()); |
Converting the other direction (from collection of wrapper objects to collection of wrapped objects) can be similarly compared to demonstrate how JDK 8 changes this. The next two code listings show the old way and the JDK 8 way.
Converting Collection of Wrapper Objects Into Collection of Wrapped Objects
1 2 3 4 5 | final Set<Movie> newMovies1 = new HashSet(); for ( final MovieWrapper wrappedMovie : wrappedMovies1) { newMovies1.add(wrappedMovie.getWrappedMovie()); } |
Converting Collection of Wrapper Objects Into Collection of Wrapped Objects – JDK 8
1 2 | final Set<Movie> newMovies2 = wrappedMovies2.stream().map(MovieWrapper::getWrappedMovie).collect(Collectors.toSet()); |
Like some of the examples in my post Stream-Powered Collections Functionality in JDK 8, the examples in this post demonstrate the power of aggregate operations provided in JDK 8. The advantages of these aggregate operations over traditional iteration include greater conciseness in the code, arguably (perhaps eventually) greater readability, and the advantages of internal iteration (including easier potential streams-supported parallelization). A good example of using streams and more complex Functions to convert between collections of less cohesively related objects is shown in Transform object into another type with Java 8.
Reference: | Using JDK 8 Streams to Convert Between Collections of Wrapped Objects and Collections of Wrapper Objects from our JCG partner Dustin Marx at the Inspired by Actual Events blog. |
Nice! Just as a further simplification, the constructor can be referenced rather than inlined:
movies.stream().map(MovieWrapper::new).collect(Collectors.toSet());