在Stream.reduce()这样的API中选择不变性有什么好理由?

回顾Java 8 Stream API设计,我对Stream.reduce()参数的generics不变性感到惊讶:

  U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) 

相同API的看似更通用的版本可能对U单个引用应用了协方差/逆变,例如:

  U reduce(U identity, BiFunction accumulator, BiFunction combiner) 

这将允许以下目前无法实现的目标:

 // Assuming we want to reuse these tools all over the place: BiFunction numberAdder = (t, u) -> t.doubleValue() + u.doubleValue(); // This currently doesn't work, but would work with the suggestion Stream 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( T1 arg1, T2 arg2 ) 

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

或者,换句话说,我可能忽略了建议设计的注意事项(例如类型推断困难,并行化约束或特定于约简操作的约束,例如关联性,对BiFunction的未来Java声明 – 站点方差的BiFunction ,…)?

爬过lambda开发的历史并隔离这个决定的“原因”的原因很难 – 所以最终,人们将不得不等待其中一个开发人员回答这个问题。

一些提示可能如下:

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

  • 有趣的是,很长一段时间,lambda提议包括一个专用接口Combiner 。 与直觉相反,这不是Stream#reduce函数中的combiner 。 相反,它被用作reducer ,这似乎是现在被称为accumulator 。 但是, 在稍后的版本中 , Combiner接口已替换为BiFunction

  • 与此问题最引人注目的相似之处在于有关邮件列表中Stream#flatMap签名的post ,然后将其转换为关于流方法签名的差异的一般问题。 例如,他们在某些地方修复了这些问题

    正如布莱恩纠正我:

    Stream flatMap(Function> mapper);

    代替:

    Stream flatMap(Function> mapper);

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

    T reduce(T identity, BinaryOperator accumulator);

    Optional reduce(BinaryOperator accumulator);

    无法修复,因为他们使用’BinaryOperator’,但如果使用’BiFunction’,那么我们有更多的灵活性

    U reduce(U identity, BiFunction accumulator, BinaryOperator combiner)

    代替:

    U reduce(U identity, BiFunction accumulator, BinaryOperator combiner);

    关于’BinaryOperator’的相同评论

    (我强调)。


我发现没有BinaryOperator替换BinaryOperator的唯一理由最终是在同一个线程中对此语句的响应中给出的:

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

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

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

此外, .reduce()参数通常以lambda表达式的forms提供,因此当这些表达式通常不包含或非常简单的业务逻辑并因此仅使用一次时,拥有更多通用版本并不重要。

例如,我是一个非常棒的jOOQ库的用户,也是一个喜欢generics谜题的好奇的Java开发人员,但是当我因为Result中的类型参数而必须在我自己的接口中放入通配符时,我经常会想念SQL元组的简单性Result以及它在处理记录类型的接口时产生的麻烦 – 而不是它是一个jOOQ故障