24

回顾 Java 8 StreamAPI 设计,我对参数的通用不变性感到惊讶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)定义如下,使用声明站点差异,这意味着任何使用的 APIFunc都可以免费获得此行为:

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

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

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

4

2 回答 2

12

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

一些提示可能如下:

  • 流接口经历了多次迭代和重构。在Stream接口的最早版本之一中,已经有专门reduce的方法,并且与问题中的方法最接近的reduce方法当时仍然被调用Stream#fold。这个已经收到了一个BinaryOperator作为combiner参数。

  • 有趣的是,在相当长的一段时间里,lambda 提案都包含了一个专用的接口Combiner<T,U,R>。与直觉相反,这没有用作函数combiner中的Stream#reduce。相反,它被用作reducer,这似乎是现在所说的accumulator。但是,该Combiner接口在以后的版本中被替换为BiFunction

  • 与这里的问题最惊人的相似之处是在邮件列表中关于签名的线程Stream#flatMap中找到,然后将其转换为关于流方法签名的方差的一般问题。他们在某些地方修复了这些,例如

    正如布赖恩纠正我的那样:

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

    代替:

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

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

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

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

    无法修复,因为他们使用了“BinaryOperator”,但如果使用了“BiFunction”,那么我们就有了更大的灵活性

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

    代替:

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

    关于“BinaryOperator”的相同评论

    (我强调)。


我发现没有BinaryOperator用a替换的唯一理由BiFunction最终在同一个线程中对该声明的响应中给出:

BinaryOperator 不会被 BiFunction 取代,即使如您所说,它引入了更大的灵活性, BinaryOperator 要求两个参数和返回类型相同,因此它在概念上具有更大的权重(EG 已经对此进行了投票)。

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

于 2016-02-28T17:03:50.960 回答
1

在我看来,只是提议的增强没有真正的用例。提议的 Javadoc 多了 3 个类型参数和 5 个通配符。我想将整个事情简化为官方 API 就足够了,因为普通的 Java 开发人员不希望(通常甚至无法)为了让编译器满意而失去理智。仅作记录,您reduce()的类型签名中只有 165 个字符。

此外,to 的参数通常以 lambda 表达式的形式提供,因此当此类表达式通常不包含或包含非常简单的业务逻辑并因此仅使用一次时,拥有更多通用版本.reduce()并没有真正意义。

例如,我是您出色的 jOOQ 库的用户,也是一个好奇的 Java 开发人员,喜欢泛型难题,但由于类型参数 inResult<T>和它在处理记录类型的接口时产生的那种麻烦 - 并不是它是一个 jOOQ 错误

于 2016-02-28T09:44:14.003 回答