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

2022-09-01 06:51:52

假设您有一个数字。中的值可以是 类型 ,等等。当您声明此类时,可以使用通配符 () 或不使用通配符来声明它。ListListIntegerDoubleList?

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

所以,现在我想把这一切都交给一个使用(显然下面的代码只是一个说明问题的例子)。让我们从流式传输开始:streamListcollectMapCollectors.toMapnumberList

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

但是,我不能在: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));

编译器在调用 时报告,并显示以下消息:number.intValue()

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

从编译器错误中可以明显看出,lambda 中的 the 被视为 a 而不是 .numberObjectNumber

所以,现在回答我的问题:

  • 当收集 通配符版本的 时,为什么它不像 非通配符版本的 ?ListList
  • 为什么 lambda 中的变量被视为 a 而不是 a?numberObjectNumber

答案 1

这是类型推断,没有正确理解。如果显式提供类型参数,它将按预期工作:

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

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

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

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


答案 2

该形式的声明意味着“具有未知类型的列表,该列表是或子类为”。有趣的是,如果未知类型由名称引用,则具有未知类型的相同类型的列表可以工作:List<? extends Number> wildcardListNumberNumber

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));
}

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

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

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


推荐