Java 8 Streams:为什么Collectors.toMap对于带有通配符的generics有不同的行为?

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

 final List numberList = Arrays.asList(1, 2, 3D); final List wildcardList = Arrays.asList(1, 2, 3D); 

所以,现在我想stream传输List并使用Collectors.toMap collect它全部collectMap (显然下面的代码只是一个例子来说明问题)。 让我们开始流式传输numberList

 final List 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)); 

但是,我不能对wildcardList执行相同的操作:

 final List 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)); 

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

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

从编译器错误中可以看出,lambda中的number被视为Object而不是Number

所以,现在问我的问题:

  • 收集List的通配符版本时,为什么它不像List的非通配符版本那样工作?
  • 为什么lambda中的number变量被认为是Object而不是Number

这种类型推断并没有得到正确的解决方案。 如果明确提供type参数,它将按预期工作:

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

这是一个已知的javac错误: 推断不应该将捕获变量映射到它们的上限 。 根据Maurizio Cimadamore的说法,

尝试修复然后因为它在8中破坏案例而退出,所以我们在8中做了一个更保守的修复而在9中完成了整个事情。

显然这个修复还没有推出。 (感谢JoelBorggrén-Franck指出我正确的方向。)

表格List wildcardList的声明List wildcardList List wildcardList表示“具有未知类型的列表,即NumberNumber的子类”。 有趣的是,如果未知类型由名称引用,则具有未知类型的相同类型的列表有效:

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

在这里, N仍然是“未知类型是NumberNumber的子类”,但您可以按预期处理List 。 您可以分配List ListList没有问题,因为未知类型extends Number的约束extends Number是兼容的。

 final List wildCardList = Arrays.asList(1, 2, 3D); doTheThingWithoutWildCards(wildCardList); // or: doTheThingWithoutWildCards(Arrays.asList(1, 2, 3D)); 

关于类型推断的章节并不容易阅读。 我不知道在这方面通配符和其他类型之间是否存在差异,但我认为不应该存在。 因此, 无论是编译器错误 还是规范限制,但从逻辑上讲,通配符都没有理由不起作用。

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

但在第二种情况下你宣布了final List wildCardList final List wildCardList因为Collectors.toMap被转换为类似Collectors.toMap Eg

  final List wildCardList = Arrays.asList(1, 2, 3D); Collector> 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); 

由此表达

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

变量类型是Object ,类Object中没有定义intValue()方法。 因此,您会收到编译错误。

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

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

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

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

你可以做:

 final List numberList = Arrays.asList(1, 2, 3D, 4D); numberList.stream().collect(Collectors.toMap(Number::intValue, Function.identity()));