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

时间:2015-01-11 17:56:29

标签: java generics lambda java-8 collectors

假设您有List个数字。 List中的值可以是IntegerDouble等类型。当您声明这样的List时,可以使用通配符声明它({{1} })或没有通配符。

?

所以,现在我想final List<Number> numberList = Arrays.asList(1, 2, 3D); final List<? extends Number> wildcardList = Arrays.asList(1, 2, 3D); streamList使用collect 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));

但是,我不能对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:找不到符号
  symbol:方法intValue()
  location:java.lang.Object类型的变量数

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

所以,现在问我的问题:

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

4 个答案:

答案 0 :(得分:34)

它的类型推断没有做到。如果明确提供type参数,它将按预期工作:

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错误:Inference should not map capture variables to their upper bounds。据Maurizio Cimadamore说,这个地位,

  

尝试了一个修复程序然后退出,因为它在8中破坏了案例,所以我们在8中进行了更为保守的修复,同时在9中完成了

显然还没有推出修复程序。 (感谢 Joel Borggrén-Franck指出我正确的方向。)

答案 1 :(得分:3)

表单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>分配给List<N>而不会出现问题,因为未知类型extends Number兼容的约束。

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

chapter about Type Inference并不容易阅读。我不知道在这方面通配符和其他类型之间是否存在差异,但我认为不应该存在。所以它的要么一个compiler bug ,要么按规范限制,但逻辑上,没有理由说通配符不起作用。

答案 2 :(得分:2)

这是由于type inference,在第一种情况下,您声明List<Number>,因此编译器在编写number -> Integer.valueOf(number.intValue())时没有任何反对意见,因为变量number的类型是{{1} }

但是在第二种情况下,你宣布了java.lang.Number  final List<? extends Number> wildCardList被翻译为Collectors.toMap例如

Collectors.<Object, ?, Map<Object, Number>toMap

因此在表达式

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()的类型是对象,并且在类Object中没有定义方法number。因此,您会收到编译错误。

您需要传递收集器类型参数,以帮助编译器解决intValue()错误例如。

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

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

答案 3 :(得分:-1)

你可以这样做:

View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
ActionBar actionBar = getActionBar();
actionBar.hide();