为什么 Stream.reduce 采用 BinaryOperator<T> 而不是 BiFunction<T, T, T>?

wil*_*mol 9 java reducing java-8 java-stream

对于我的具体情况,我想减少使用功能组合;例如:

BiFunction<ImmutableSet<Integer>, ImmutableSet<Integer>, Sets.SetView<Integer>> f = Sets::intersection;
Function<Sets.SetView<Integer>, ImmutableSet<Integer>> g = Sets.SetView::immutableCopy;
BiFunction<ImmutableSet<Integer>, ImmutableSet<Integer>, ImmutableSet<Integer>> biFunction = f.andThen(g);
ImmutableSet<Integer> intersection = Stream.of(ImmutableSet.of(1, 2, 3), ImmutableSet.of(1, 2), ImmutableSet.of(4))
    .reduce(biFunction)
    .orElse(ImmutableSet.of());
Run Code Online (Sandbox Code Playgroud)

这有一个编译错误:

BiFunction<ImmutableSet<Integer>, ImmutableSet<Integer>, Sets.SetView<Integer>> f = Sets::intersection;
Function<Sets.SetView<Integer>, ImmutableSet<Integer>> g = Sets.SetView::immutableCopy;
BiFunction<ImmutableSet<Integer>, ImmutableSet<Integer>, ImmutableSet<Integer>> biFunction = f.andThen(g);
ImmutableSet<Integer> intersection = Stream.of(ImmutableSet.of(1, 2, 3), ImmutableSet.of(1, 2), ImmutableSet.of(4))
    .reduce(biFunction)
    .orElse(ImmutableSet.of());
Run Code Online (Sandbox Code Playgroud)

相反,我需要这样做:

ImmutableSet<Integer> intersection = Stream.of(ImmutableSet.of(1, 2, 3), ImmutableSet.of(1, 2), ImmutableSet.of(4))
    .reduce((a, b) -> Sets.intersection(a, b).immutableCopy())
    .orElse(ImmutableSet.of());
Run Code Online (Sandbox Code Playgroud)

然而,这失去了组合提供的无点风格

为什么 Stream API 是这样设计的?ABinaryOperator是 a BiFunction,所以reduce用超类型声明方法的参数不是更有意义吗?

Scr*_*tte 5

reduce 操作必须采用相同类型的参数并返回相同类型。如果没有,就会出现类型不匹配。原来BinaryOperator是这样:BinaryOperator<T> extends BiFunction<T,T,T>

您可以创建您的BiFunction. 然后创建一个BinaryOperator

import java.util.function.BinaryOperator;
import java.util.function.BiFunction;
import java.util.function.Function;

import java.util.stream.Stream;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

public class StackOverflowTest {
  public static void main(String[] args) {

    BiFunction<ImmutableSet<Integer>, ImmutableSet<Integer>, Sets.SetView<Integer>> f = Sets::intersection;
    Function<Sets.SetView<Integer>, ImmutableSet<Integer>> g = Sets.SetView::immutableCopy;

    BiFunction<ImmutableSet<Integer>, ImmutableSet<Integer>, ImmutableSet<Integer>> biFunction = f.andThen(g);

    BinaryOperator<ImmutableSet<Integer>> biOperator = biFunction::apply;

    ImmutableSet<Integer> intersection =
       Stream.of(ImmutableSet.of(1, 2, 3),
                 ImmutableSet.of(1, 2),
                 ImmutableSet.of(1, 4)) // added a 1
             .reduce(biOperator)
             .orElse(ImmutableSet.of());

    System.out.println(intersection);

/*
prints:
[1]
*/
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 但这并不能回答为什么 API *需要* `BinaryOperator&lt;T&gt;` 的问题,而更通用的 `BiFunction&lt;T, T, T&gt;` 就可以正常工作,并且调用者可以提供一个 `BiFunction&lt;T , T, T&gt;` 通过便利类 `BinaryOperator&lt;T&gt;` 如果他们愿意的话,而不是被迫使用子类型。这忽略了里氏替换原则。 (7认同)
  • @Bohemian,它已在[这个旧答案](/sf/answers/2497985801/)中得到回答。好吧,理由不需要令人信服。但至少,我们知道开发者对此有何评论…… (6认同)
  • @Eugene `Block&lt;T&gt;` 是 `Consumer&lt;T&gt;` 的前身。 (2认同)