Java Streams: Override return type of Collectors.mapping

Viewed 181

This is my code:

Map<String, Collection<? extends String>> test = listOfTipusIdentificadorPacient.stream()
    .collect(Collectors.groupingBy(
        TipusIdentificadorPacient::getOid,
        Collectors.mapping(TipusIdentificadorPacient::getUse, Collectors.toList())
    )
);

I'm getting this compilation message:

Type mismatch: cannot convert from Map<String,List> to Map<String,Collection<? extends String>>

I don't quite figure out how to override Collectors.mapping in order to:

return:
  Map<String,Collection<? extends String>>
instead of:
  Map<String,List<String>>

I've tried to create another generic code in order to make it to compile.

Code is:

Stream<Map.Entry<String, String>> streamOfPairedStrings = Stream.of();
Map<String, Collection<? extends String>> test = streamOfPairedStrings
    .collect(Collectors.groupingBy(
        Map.Entry::getKey,
        Collectors.mapping(Pair::getValue, Collectors.toList())
    )
);

Any ideas?

1 Answers

The reason for the compilation error is thus:

Map<String, List<String>> mapOfLists = Map.of();
Map<String, Collection<? extends String>> mapOfCollections = Map.of();

Consider that this code would then be legal:

mapOfCollections.put("", Set.of());

That is, you can put key/value pairs where the value isn't a List<String>. As such, you can't assign:

mapOfCollections = mapOfLists;

because then you could do the put above, leading to heap pollution. The compiler just stops you doing it.

// If it were legal...
mapOfCollections = mapOfLists;
mapOfCollections.put("", Set.of());
List<String> list = mapOfLists.get(""); // ClassCastException!

I think you could do this with Collectors.collectingAndThen around the toList(), where the "and then" is an unconstrained cast:

Collectors.collectingAndThen(Collectors.toList(), a -> a)

The reason you can't do this with Function.identity() is the combination of the signature of collectingAndThen and Function.identity():

  • collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher) means that the input type of the function has to match the output type of the collector - in your case, List<String>.
  • Function.identity() is a Function<T, T> (with no wildcards). Since the input type of the function has to be List<String>, its output type is also List<String>.

a -> a looks like the identity function, but it's actually more generic than that: it's an upcast function, Function<? extends T, T>, meaning the output type doesn't have to be exactly the same as the input, but it's something that can be cast safely.

Thus, here, a -> a acts as a Function<List<String>, Collection<? extends String>> (because List<String> is a subtype of Collection<String>, which is a subtype of Collection<? extends String>).

Related