【问题标题】:Stream reduction with wildcard types使用通配符类型减少流
【发布时间】:2015-06-25 04:44:08
【问题描述】:

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

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

这适用于任何List&lt;Number&gt;,但是如果我希望能够减少? 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() 必须返回与源元素相同类型的东西,并且? extends NumberNumber 不同。但我不确定如何处理这种情况。如何允许减少(或收集)子类(例如 List&lt;Integer&gt;)的集合?


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

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

【问题讨论】:

  • @SotiriosDelimanolis 不是直接的,而是很好的建议!我会发布更多答案。
  • 如果您的论点是 BigDecimals 列表,(Number)0 意味着什么?
  • @assylias 取决于可能导致问题的累加器函数,但在这种情况下它不应该 - 我想要某种Number,不一定是BigDecimalSet 示例可能会阐明何时这可能有用。我想返回某种Set,我不在乎它是什么实现。

标签: java generics java-8 java-stream


【解决方案1】:

问题实际上不在于? extends,而在于identity 参数reduce()。正如 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 是不同的类型,因此您必须复制归约函数,这有点烦人。您大概可以在变量中定义一次并通过不安全的强制转换将其传递给两者,但我不确定这是一种改进。无论如何,使用方法引用可能是正确的方法。

【讨论】:

  • 您可以简化您的“Sets 解决方案”以完全避免强制转换并由于方法表达式而具有更短的语法:sets.stream().&lt;Set&lt;E&gt;&gt; reduce(ImmutableSet.&lt;E&gt; of(), Sets::union, Sets::union);
【解决方案2】:

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

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

【讨论】:

  • 是的,您可以直接转换所有内容(您也可以使用Class.cast() 作为方法参考,例如.map(Number.class::cast))。使用三参数 reduce() 重载在概念上感觉更“正确”,但对我来说 - 我们从 T 流开始并减少到值 U
  • (a, b) -&gt; a 结果为 0?
猜你喜欢
  • 2021-07-10
  • 2017-12-08
  • 2015-09-15
  • 1970-01-01
  • 1970-01-01
  • 2021-07-17
  • 1970-01-01
  • 1970-01-01
  • 2019-02-09
相关资源
最近更新 更多