Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Streams: why does Collectors.toMap behave differently for generics with wildcards?

Assume that you have a List of numbers. The values in the List can be of type Integer, Double etc. When you declare such a List it is possible to declare it using a wildcard (?) or without a wildcard.

final List<Number> numberList = Arrays.asList(1, 2, 3D); final List<? extends Number> wildcardList = Arrays.asList(1, 2, 3D); 

So, now I want to stream over the List and collect it all to a Map using the Collectors.toMap (obviously the code below is just an example to illustrate the problem). Lets start off by streaming the numberList:

final List<Number> numberList = Arrays.asList(1, 2, 3D, 4D);  numberList.stream().collect(Collectors.toMap(         // Here I can invoke "number.intValue()" - the object ("number") is treated as a Number         number -> Integer.valueOf(number.intValue()),         number -> number)); 

But, I can not do the same operation on the wildcardList:

final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D); wildCardList.stream().collect(Collectors.toMap(         // Why is "number" treated as an Object and not a Number?         number -> Integer.valueOf(number.intValue()),         number -> number)); 

The compiler complains on the call to number.intValue() with the following message:

Test.java: cannot find symbol
symbol: method intValue()
location: variable number of type java.lang.Object

From the compiler error it is obvious that the number in the lambda is treated as an Object instead of as a Number.

So, now to my question(s):

  • When collecting the wildcard version of the List, why is it not working like the non-wildcard version of the List?
  • Why is the number variable in the lambda considered to be an Object instead of a Number?
like image 696
wassgren Avatar asked Jan 11 '15 17:01

wassgren


People also ask

What is Merge function in collectors toMap?

Collectors.toMap() with Mapper and Merge FunctionsIt's input are two values that is the two values for which keyMapper returned the same key, and merges those two values into a single one.

What is Collectors toList ()?

The toList() method of Collectors Class is a static (class) method. It returns a Collector Interface that gathers the input data onto a new list. This method never guarantees type, mutability, serializability, or thread-safety of the returned list but for more control toCollection(Supplier) method can be used.

Does Stream Map maintain order?

A parallel stream is performed one or more elements at a time. Thus the map() would preserve the encounter of the stream order but not the original List's order.

What is the use of collectors in Java 8?

collect() is one of the Java 8's Stream API's terminal methods. It allows us to perform mutable fold operations (repackaging elements to some data structures and applying some additional logic, concatenating them, etc.) on data elements held in a Stream instance.


1 Answers

It's the type inference that doesn't get it right. If you provide the type argument explicitly it works as expected:

List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D); wildCardList.stream().collect(Collectors.<Number, Integer, Number>toMap(                                   number -> Integer.valueOf(number.intValue()),                                   number -> number)); 

This is a known javac bug: Inference should not map capture variables to their upper bounds. The status, according to Maurizio Cimadamore,

a fix was attempted then backed out as it was breaking cases in 8, so we went for a more conservative fix in 8 while doing the full thing in 9

Apparently the fix has not yet been pushed. (Thanks to Joel Borggrén-Franck for pointing me in the right direction.)

like image 91
aioobe Avatar answered Sep 23 '22 17:09

aioobe