Java 8 Streams:为什么 Collectors.toMap 对于带有通配符的泛型有不同的表现?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27890241/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 05:13:58  来源:igfitidea点击:

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

javagenericslambdajava-8collectors

提问by wassgren

Assume that you have a Listof numbers. The values in the Listcan be of type Integer, Doubleetc. When you declare such a Listit is possible to declare it using a wildcard (?) or without a wildcard.

假设你有一个List数字。在该值List可以是类型的IntegerDouble等等。当你声明这样的List有可能使用一个通配符(声明它?)或不用一个通配符。

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

So, now I want to streamover the Listand collectit all to a Mapusing the Collectors.toMap(obviously the code below is just an example to illustrate the problem). Lets start off by streaming the numberList:

所以,现在我想streamListcollect它所有的Map使用Collectors.toMap(显然下面的代码只是为了说明问题的例子)。让我们从流式传输开始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:

但是,我不能对以下内容进行相同的操作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:

编译器在调用 时抱怨number.intValue()以下消息:

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

Test.java:找不到符号
符号:方法 intValue()
位置:java.lang.Object 类型的变量数

From the compiler error it is obvious that the numberin the lambda is treated as an Objectinstead of as a Number.

从编译器错误很明显,numberlambda 中的 被视为 anObject而不是 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 numbervariable in the lambda considered to be an Objectinstead of a Number?
  • 在收集 的通配符版本时List,为什么它不像 的非通配符版本那样工作List
  • 为什么numberlambda 中的变量被认为是 anObject而不是 a Number

采纳答案by 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,

这是一个已知的 javac 错误:推理不应将捕获变量映射到它们的上限。根据 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

尝试修复然后因为它在 8 中破坏案例而退出,因此我们在 8 中进行了更保守的修复,同时在 9 中完成了全部工作

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

显然,该修复程序尚未推送。(感谢Joel Borggrén-Franck为我指明了正确的方向。)

回答by sol4me

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 numberisjava.lang.Number

这是由于类型推断,在第一种情况下,您声明List<Number>编译器在编写时没有任何反对,number -> Integer.valueOf(number.intValue())因为变量的类型numberjava.lang.Number

But in second case you declared final List<? extends Number> wildCardListdue to which Collectors.toMapis translated to something like Collectors.<Object, ?, Map<Object, Number>toMapE.g.

但在第二种情况下,你声明final List<? extends Number> wildCardList由于 which Collectors.toMap被翻译成类似Collectors.<Object, ?, Map<Object, Number>toMapEg

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

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

type of variable numberis Objectand there is no method intValue()defined in class Object. Hence you get compilation error.

变量类型numberObjectintValue(),类Object中没有定义方法。因此你会得到编译错误。

What you need is to pass collector type arguments which helps the compiler to resolve intValue()error E.g.

您需要的是传递收集器类型参数,这有助于编译器解决 intValue()错误,例如

    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::intValueinstead of number -> Integer.valueOf(number.intValue())

此外,您可以使用方法引用Number::intValue而不是number -> Integer.valueOf(number.intValue())

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

有关 Java 8 中类型推断的更多详细信息,请参阅此处

回答by Holger

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

形式的声明List<? extends Number> wildcardList意味着一个“具有未知类型的列表,它是Number或 的子类Number”。有趣的是,如果未知类型由名称引用,则具有未知类型的相同类型的列表有效:

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, Nis still “an unknown type being Numberor 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 Numberis compatible.

在这里,N仍然是“未知类型Number或子类Number”,但您可以List<N>按预期处理。您可以毫无问题地将 分配List<? extends Number>给 aList<N>作为未知类型extends Number兼容的约束。

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

The chapter about Type Inferenceis 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.

关于类型推断章节并不容易阅读。我不知道通配符和其他类型在这方面是否有区别,但我认为不应该有。所以它要么编译器错误,要么是规范限制,但从逻辑上讲,通配符没有理由不工作。

回答by Rory G

You can do:

你可以做:

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

numberList.stream().collect(Collectors.toMap(Number::intValue, Function.identity()));