35

假设你有一个List数字。中的值List可以是 typeIntegerDouble。当您声明这样的 aList时,可以使用通配符 ( ?) 或不使用通配符来声明它。

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

所以,现在我想stream通过使用(显然下面的代码只是说明问题的一个示例)。让我们从流式传输开始:ListcollectMapCollectors.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:找不到符号
符号:方法 intValue()
位置:java.lang.Object 类型的变量号

从编译器错误中,很明显numberlambda 中的 被视为 anObject而不是 a Number

所以,现在我的问题:

  • 在收集 的通配符版本时List,为什么它不像非通配符版本的List
  • 为什么numberlambda 中的变量被认为是 aObject而不是 a Number
4

4 回答 4

43

这是类型推断不正确。如果您明确提供类型参数,它会按预期工作:

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为我指明了正确的方向。)

于 2015-01-11T18:05:17.837 回答
4

表单的声明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));
}

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

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

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

于 2015-01-12T12:05:14.267 回答
2

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

但是在第二种情况下,您声明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);

结果在表达式中

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

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

您需要传递收集器类型参数,这有助于编译器解决 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);

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

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

于 2015-01-11T18:18:08.553 回答
-1

You can do:

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

numberList.stream().collect(Collectors.toMap(Number::intValue, Function.identity()));
于 2018-05-18T08:58:31.613 回答