Java 8 Streams:为什么Collectors.toMap对于带有通配符的generics有不同的行为?
假设您有一个数字List
。 List
的值可以是Integer
, Double
等类型。当您声明这样的List
,可以使用通配符( ?
)或不使用通配符来声明它。
final List numberList = Arrays.asList(1, 2, 3D); final List wildcardList = Arrays.asList(1, 2, 3D);
所以,现在我想stream
传输List
并使用Collectors.toMap
collect
它全部collect
到Map
(显然下面的代码只是一个例子来说明问题)。 让我们开始流式传输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 extends Number> 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 extends Number> wildcardList
的声明List extends Number> wildcardList
List extends Number> wildcardList
表示“具有未知类型的列表,即Number
或Number
的子类”。 有趣的是,如果未知类型由名称引用,则具有未知类型的相同类型的列表有效:
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
仍然是“未知类型是Number
或Number
的子类”,但您可以按预期处理List
。 您可以分配List extends Number>
List extends Number>
为List
没有问题,因为未知类型extends Number
的约束extends Number
是兼容的。
final List extends Number> 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 extends Number> wildCardList
final List extends Number> wildCardList
因为Collectors.toMap
被转换为类似Collectors.
Eg
final List extends Number> wildCardList = Arrays.asList(1, 2, 3D); Collector
由此表达
number -> Integer.valueOf(number.intValue()
变量类型是Object ,类Object中没有定义intValue()
方法。 因此,您会收到编译错误。
你需要的是传递收集器类型参数,这有助于编译器解决intValue()
错误例如
final List extends Number> 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()));