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 theList
? - Why is the
number
variable in the lambda considered to be anObject
instead of aNumber
?
This is due to type inference, In first case you declared
List<Number>
so compiler have nothing against when you writenumber -> Integer.valueOf(number.intValue())
because type of variablenumber
isjava.lang.Number
But in second case you declared
final List<? extends Number> wildCardList
due to whichCollectors.toMap
is translated to something likeCollectors.<Object, ?, Map<Object, Number>toMap
E.g.As a result of which in expression
number -> Integer.valueOf(number.intValue()
type of variable
number
is Object and there is no methodintValue()
defined in class Object. Hence you get compilation error.What you need is to pass collector type arguments which helps the compiler to resolve
intValue()
error E.g.Moreover you can use method reference
Number::intValue
instead ofnumber -> Integer.valueOf(number.intValue())
For more details on Type Inference in Java 8 please refer here.
You can do:
It's the type inference that doesn't get it right. If you provide the type argument explicitly it works as expected:
This is a known javac bug: Inference should not map capture variables to their upper bounds. The status, according to Maurizio Cimadamore,
Apparently the fix has not yet been pushed. (Thanks to Joel Borggrén-Franck for pointing me in the right direction.)
The declaration of the form
List<? extends Number> wildcardList
implies a “list with an unknown type which isNumber
or a subclass ofNumber
”. Interestingly, the same kind of list with unknown type works, if the unknown type is referred by a name:Here,
N
is still “an unknown type beingNumber
or a subclass ofNumber
” but you can process theList<N>
as intended. You can assign theList<? extends Number>
to aList<N>
without problems as the constraint that the unknown typeextends Number
is compatible.The chapter about Type Inference is not an easy read. I don’t know if there is a difference between wildcards and other types in this regard, but I don’t think that there should be. So its
eithera compiler bugor a limitation by specification but logically,there is no reason why the wildcard shouldn’t work.