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

馋奶兔 提交于 2019-11-30 02:58:09
aioobe

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.)

The declaration of the form List<? extends Number> wildcardList implies a “list with an unknown type which is Number or a subclass of Number”. Interestingly, the same kind of list with unknown type works, if the unknown type is referred by a name:

static <N extends Number> void doTheThingWithoutWildCards(List<N> numberList) {
    numberList.stream().collect(Collectors.toMap(
      // Here I can invoke "number.intValue()" - the object is treated as a Number
      number -> number.intValue(),
      number -> number));
}

Here, N is still “an unknown type being Number or a subclass of Number” but you can process the List<N> as intended. You can assign the List<? extends Number> to a List<N> without problems as the constraint that the unknown type extends Number is compatible.

final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
doTheThingWithoutWildCards(wildCardList); // or:
doTheThingWithoutWildCards(Arrays.asList(1, 2, 3D));

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 either a compiler bug or a limitation by specification but logically, there is no reason why the wildcard shouldn’t work.

This is due to type inference, In first case you declared List<Number> so compiler have nothing against when you write number -> Integer.valueOf(number.intValue()) because type of variable number isjava.lang.Number

But in second case you declared final List<? extends Number> wildCardList due to which Collectors.toMap is translated to something like Collectors.<Object, ?, Map<Object, Number>toMap E.g.

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

As a result of which in expression

number -> Integer.valueOf(number.intValue()

type of variable number is Object and there is no method intValue() 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.

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


    Collector<Number, ?, Map<Integer, Number>> collector = Collectors.<Number, Integer, Number>toMap(
            // Why is number treated as an Object and not a Number?
            Number::intValue,
            number -> number);
    wildCardList.stream().collect(collector);

Moreover you can use method reference Number::intValue instead of number -> Integer.valueOf(number.intValue())

For more details on Type Inference in Java 8 please refer here.

You can do:

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

numberList.stream().collect(Collectors.toMap(Number::intValue, Function.identity()));
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!