使用通配符类型减少流

Stream reduction with wildcard types

我正在试验 Stream.reduce(),运行 遇到了类型系统的问题。这是一个玩具示例:

public static Number reduceNum1(List<Number> nums) {
  return nums.stream().reduce(0, (a, b) -> a);
}

这适用于任何 List<Number>,但如果我希望能够减少 ? extends Number 的列表怎么办?这不编译:

public static Number reduceNum2(List<? extends Number> nums) {
    return nums.stream().reduce((Number)0, (a, b) -> a);
}

出现错误:

ReduceTest.java:72: error: no suitable method found for reduce(Number,(a,b)->a)
        return nums.stream().reduce((Number)0, (a, b) -> a);
                            ^
    method Stream.reduce(CAP#1,BinaryOperator<CAP#1>) is not applicable
      (argument mismatch; Number cannot be converted to CAP#1)
    method Stream.<U>reduce(U,BiFunction<U,? super CAP#1,U>,BinaryOperator<U>) is not applicable
      (cannot infer type-variable(s) U
        (actual and formal argument lists differ in length))
  where U,T are type-variables:
    U extends Object declared in method <U>reduce(U,BiFunction<U,? super T,U>,BinaryOperator<U>)
    T extends Object declared in interface Stream
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Number from capture of ? extends Number
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output

从概念上讲,我明白为什么会这样。 Stream.reduce() 必须 return 与源元素的类型相同,并且 ? extends NumberNumber 不同。但我不确定如何处理这种情况。如何允许减少(或收集)子类(例如 List<Integer>)的集合?


如果有帮助,这里有一个更实际的例子,同样无法编译:

public static <E> Set<E> reduceSet1(List<? extends Set<E>> sets) {
  return sets.stream().reduce(ImmutableSet.<E>of(), (a, b) -> Sets.union(a, b));
}

问题实际上不是 ? extends,而是 reduce()identity 参数。正如 Sotirios Delimanolis 所建议的,您可以指定一个有界类型 N extends Number,但前提是标识值为 null.

public static <N extends Number> N reduceNum3(List<N> nums) {
  return nums.stream().reduce(null, (a, b) -> a);
}

这是因为通配符和有界方法都无法确定身份参数是否与列表元素的类型相同(除非是 null,所有类型都共享)。

解决方法是使用三参数 reduce() 方法,它允许您将结果视为不同的类型(即使它不是 真的 )。

这是 Number 示例:

public static Number reduceNum4(List<? extends Number> nums) {
  return nums.stream().reduce((Number)0,
    (a, b) -> a,
    (a, b) -> a);
}

这里是 Set 示例:

public static <E> Set<E> reduceSet2(List<? extends Set<E>> sets) {
  return sets.stream().<Set<E>>reduce(
      ImmutableSet.<E>of(), Sets::union, Sets::union);
}

有点恼人的是,您必须复制归约函数,因为 accumulatorcombiner 是不同的类型。您大概可以在一个变量中定义它一次,然后通过不安全的转换将它传递给两者,但我不确定这是一种改进。无论如何,使用方法引用可能是正确的方法。

我遇到了类似的问题,解决方法如下

public static Number reduceNum2(List<? extends Number> nums) {
    return nums.stream().map(num->(Number) num).reduce(0, (a, b) -> a);
}