java - 在像 Stream.reduce() 这样的 API 中选择不变性的充分理由是什么?

标签 java java-stream covariance contravariance invariance

审查 Java 8 Stream API 设计,我对 Stream.reduce() 上的通用不变性感到惊讶论据:

<U> U reduce(U identity,
             BiFunction<U,? super T,U> accumulator,
             BinaryOperator<U> combiner)

同一个 API 的一个看似更通用的版本可能对 U 的各个引用应用了协变/逆变。 ,如:

<U> U reduce(U identity,
             BiFunction<? super U, ? super T, ? extends U> accumulator,
             BiFunction<? super U, ? super U, ? extends U> combiner)

这将允许以下情况,目前这是不可能的:

// Assuming we want to reuse these tools all over the place:
BiFunction<Number, Number, Double> numberAdder =
    (t, u) -> t.doubleValue() + u.doubleValue();

// This currently doesn't work, but would work with the suggestion
Stream<Number> stream = Stream.of(1, 2L, 3.0);
double sum = stream.reduce(0.0, numberAdder, numberAdder);

解决方法,使用方法引用将类型“强制”为目标类型:

double sum = stream.reduce(0.0, numberAdder::apply, numberAdder::apply);

C# 没有这个特殊问题,如 Func(T1, T2, TResult) 定义如下,使用声明站点差异,这意味着任何使用 Func 的 API免费获得此行为:

public delegate TResult Func<in T1, in T2, out TResult>(
    T1 arg1,
    T2 arg2
)

与建议的设计相比,现有设计有哪些优势(可能还有 EG 决策的原因)?

或者,换一种方式问,我可能忽略的建议设计的注意事项是什么(例如类型推断困难、并行化约束或特定于归约操作的约束,例如关联性、对 future Java 声明站点的预期BiFunction<in T, in U, out R> 上的差异,...)?

最佳答案

浏览 lambda 开发的历史并找出做出这一决定的“THE”原因是很困难的 - 所以最终,人们将不得不等待其中一位开发人员回答这个问题。

一些提示可能如下:

  • 流接口(interface)经历了多次迭代和重构。在 Stream 的最早版本之一中接口(interface),已经有专用reduce方法,以及最接近 reduce 的方法问题中的方法仍称为 Stream#fold 那时。这个已经收到了 BinaryOperator作为combiner范围。

  • 有趣的是,有一段时间,lambda 提案包含一个专用接口(interface) Combiner<T,U,R> .与直觉相反,这并没有用作 combinerStream#reduce功能。相反,它被用作 reducer ,这似乎是现在所说的 accumulator .但是,Combiner接口(interface)是 replaced with BiFunction in a later revision .

  • 与这里的问题最显着的相似之处在于 thread about the Stream#flatMap signature at the mailing list ,然后变成关于流方法签名的方差的一般问题。他们在某些地方修复了这些问题,例如

    As Brian correct me:

    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

    instead of:

    <R> Stream<R> flatMap(Function<T, Stream<? extends R>> mapper);

    但注意到在某些地方,这是不可能的:

    T reduce(T identity, BinaryOperator<T> accumulator);

    and

    Optional<T> reduce(BinaryOperator<T> accumulator);

    Can't be fixed because they used 'BinaryOperator', But if 'BiFunction' is used then we have more flexibility

    <U> U reduce(U identity, BiFunction<? super U, ? super T, ? extends U> accumulator, BinaryOperator<U> combiner)

    Instead of:

    <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);

    Same comment regarding 'BinaryOperator'

    (我强调)。


我发现替换BinaryOperator的唯一理由与 BiFunction最终在 response to this statement, in the same thread 中给出:

BinaryOperator will not be replaced by BiFunction even if, as you said, it introduce more flexibility, a BinaryOperator ask that the two parameters and the return type to be the same so it has conceptually more weight (the EG already votes on that).

也许有人可以挖掘出专家组对决定该决定的投票的具体引用,但也许这句话已经充分回答了为什么会这样的问题......

关于java - 在像 Stream.reduce() 这样的 API 中选择不变性的充分理由是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35680706/

相关文章:

java - 如果应用程序使用不同于 MySQL 的时区,则 Hibernate 保存/检索日期减去天数

java - Spring : How to create similar beans in Spring Boot dynamically?

java - 迭代列表查找 boolean 属性,返回默认 false

java - 使用 Collectors.toMap 如何转换 map 值

Java 8 stream API orElse 用法

c# - 如果I <D>通过方差转换可转换为I <B>,则I <D>是否重新实现I <B>?

java - Android 应用程序崩溃并在注册时出现错误

java - Graphics2D.drawPolyline() 与 Graphics2D.drawLine()

C#实现接口(interface)时的协变和逆变

jsf-2 - EL 和协变返回类型