Collecting to a Set

The method toSet() creates a collector that uses a mutable container of type Set to perform mutable reduction. The collector does not guarantee to preserve the encounter order of the input stream. For more control over the type of the set, the toCollection() method can be used.

The following stream pipeline creates a set with the titles of all CDs in the stream.

Click here to view code image

Set<String> cdTitles2 = CD.cdList.stream()       // Stream<CD>
    .map(CD::title)                              // Stream<String>
    .collect(Collectors.toSet());
//[Hot Generics, Java Jive, Lambda Dancing, Keep on Erasing, Java Jam]

Collecting to a Map

The method toMap() creates a collector that performs mutable reduction to a mutable container of type Map.

Click here to view code image

static <T,K,U> Collector<T,?,Map<K,U>> toMap(
       Function<? super T,? extends K> keyMapper,
       Function<? super T,? extends U> valueMapper)

static <T,K,U> Collector<T,?,Map<K,U>> toMap(
       Function<? super T,? extends K> keyMapper,
       Function<? super T,? extends U> valueMapper,
       BinaryOperator<U>               mergeFunction)

static <T,K,U,M extends Map<K,U>> Collector<T,?,M> toMap(
       Function<? super T,? extends K> keyMapper,
       Function<? super T,? extends U> valueMapper,
       BinaryOperator<U>               mergeFunction,
       Supplier<M>                     mapSupplier)

Return a Collector that accumulates elements of type T into a Map whose keys and values are the result of applying the provided key and value mapping functions to the input elements.

The keyMapper function produces keys of type K, and the valueMapper function produces values of type U.

In the first method, the mapped keys cannot have duplicates—an Illegal-StateException will be thrown if that is the case.

In the second and third methods, the mergeFunction binary operator is used to resolve collisions between values associated with the same key, as supplied to Map.merge(Object, Object, BiFunction).

In the third method, the provided mapSupplier function returns a new Map into which the results will be inserted.

The collector returned by the method toMap() uses either a default map or one that is supplied. To be able to create an entry in a Map<K,U> from stream elements of type T, the collector requires two functions:

  • keyMapper: T -> K, which is a Function to extract a key of type K from a stream element of type T.
  • valueMapper: T -> U, which is a Function to extract a value of type U for a given key of type K from a stream element of type T.

Additional functions as arguments allow various controls to be exercised on the map:

  • mergeFunction: (U,U) -> U, which is a BinaryOperator to merge two values that are associated with the same key. The merge function must be specified if collision of values can occur during the mutable reduction, or a resounding exception will be thrown.
  • mapSupplier: () -> M extends Map<K,V>, which is a Supplier that creates a map instance of a specific type to use for mutable reduction. The map created is a subtype of Map<K,V>. Without this function, the collector uses a default map.

Figure 16.15 illustrates collecting to a map. The stream pipeline creates a map of CD titles and their release year—that is, a Map<String, Year>, where K is String and V is Year. The keyMapper CD::title and the valueMapper CD::year extract the title (String) and the year (Year) from each CD in the stream, respectively. The entries are accumulated in a default map (Map<String, Year>).

Figure 16.15 Collecting to a Map

What if we wanted to create a map with CDs and their release year—that is, a Map<CD, Year>? In that case, the keyMapper should return the CD as the key—that is, map a CD to itself. That is exactly what the keyMapper Function.identity() does in the pipeline below.

Click here to view code image

Map<CD, Year> mapCDToYear = CD.cdList.stream()
    .collect(Collectors.toMap(Function.identity(), CD::year)); // Map<CD, Year>

As there were no duplicates of the key in the previous two examples, there was no collision of values in the map. In the list dupList below, there are duplicates of CDs (CD.cd0, CD.cd1). Executing the pipeline results in a runtime exception at (1).

Click here to view code image

List<CD> dupList = List.of(CD.cd0, CD.cd1, CD.cd2, CD.cd0, CD.cd1);
Map<String, Year> mapTitleToYear1 = dupList.stream()
    .collect(Collectors.toMap(CD::title, CD::year));       // (1)
// IllegalStateException: Duplicate key 2017

The collision values can be resolved by specifying a merge function. In the pipeline below, the arguments of the merge function (y1, y2) -> y1 at (1) have the same value for the year if we assume that a CD can only be released once. Note that y1 and y2 denote the existing value in the map and the value to merge, respectively. The merge function can return any one of the values to resolve the collision.

Click here to view code image

Map<String, Year> mapTitleToYear2 = dupList.stream()
    .collect(Collectors.toMap(CD::title, CD::year, (y1, y2) -> y1));       // (1)

The stream pipeline below creates a map of CD titles released each year. As more than one CD can be released in a year, collision of titles can occur for a year. The merge function (tt, t) -> tt + “:” + t concatenates the titles in each year separated by a colon, if necessary. Note that tt and t denote the existing value in the map and the value to merge, respectively.

Click here to view code image

Map<Year, String> mapTitleToYear3 = CD.cdList.stream()
    .collect(Collectors.toMap(CD::year, CD::title,
                              (tt, t) -> tt + “:” + t));
//{2017=Java Jive:Java Jam, 2018=Lambda Dancing:Keep on Erasing:Hot Generics}

The stream pipeline below creates a map with the longest title released each year. For greater control over the type of the map in which to accumulate the entries, a supplier is specified. The supplier TreeMap::new returns an empty instance of a TreeMap in which the entries are accumulated. The keys in such a map are sorted in their natural order—the class java.time.Year implements the Comparable<Year> interface.

Click here to view code image

TreeMap<Year, String> mapYearToLongestTitle = CD.cdList.stream()
    .collect(Collectors.toMap(CD::year, CD::title,
                              BinaryOperator.maxBy(Comparator.naturalOrder()),
                              TreeMap::new));
//{2017=Java Jive, 2018=Lambda Dancing}

The merge function specified is equivalent to the following lambda expression, returning the greater of two strings:

Click here to view code image

(str1, str2) -> str1.compareTo(str2) > 0 ? str1 : str2

Leave a Reply

Your email address will not be published. Required fields are marked *