回顾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决策的原因)?
或者,换句话说,我可能忽略了建议设计的注意事项(例如类型推断困难,并行化约束或特定于还原操作的约束,例如关联性,对未来Java声明站点的预期BiFunction<in T, in U, out R>
上的差异,...)?
答案 0 :(得分:5)
浏览lambda开发历史并隔离&#34; THE&#34;这个决定的原因很难 - 所以最终,人们将不得不等待其中一个开发人员回答这个问题。
一些提示可能如下:
流接口经历了多次迭代和重构。在Stream
界面的最早版本之一中,有专门的reduce
方法,而且问题中最接近reduce
方法的方法仍被称为Stream#fold
那时候。这个已经收到BinaryOperator
作为combiner
参数。
有趣的是,很长一段时间,lambda提案包含一个专用接口Combiner<T,U,R>
。与直觉相反,这并未用作combiner
函数中的Stream#reduce
。相反,它被用作reducer
,这似乎是现在所谓的accumulator
。但是,Combiner
界面为replaced with BiFunction
in a later revision。
这个问题最引人注目的相似之处在于thread about the Stream#flatMap
signature at the mailing list,然后将其转化为关于流方法签名的方差的一般问题。他们在某些地方修复了这些问题,例如
布赖恩纠正我:
<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&#39;,但如果&#39; BiFunction&#39;是 然后使用我们有更多的灵活性
<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);
关于&#39; BinaryOperator&#39;
的相同评论
(我强调)。
我发现不用BinaryOperator
替换BiFunction
的唯一理由最终在response to this statement, in the same thread中给出:
即使正如你所说的那样,BinaryOperator也不会被BiFunction取代 它引入了更多的灵活性,一个BinaryOperator问两个参数 和返回类型是相同的,所以它在概念上更重 (EG已经对此投了票)。
也许有人可以挖掘出专家组对该决定的投票的特定参考,但也许这句话已足以回答为什么它就是这样的问题......
答案 1 :(得分:1)
在我看来,建议的增强并没有真正的用例。提议的Javadoc还有3个类型参数和5个通配符。我认为这足以将整个事情简化为官方API,因为普通的Java开发人员不想(通常甚至不能)试图让编译器满意而失去理智。仅供记录,您的reduce()
仅在类型签名中包含165个字符。
此外,.reduce()
的参数通常以lambda表达式的形式提供,因此当这些表达式通常不包含或没有时,更通用的版本没有实际意义。非常简单的业务逻辑,因此只使用一次。
例如我是你梦寐以求的jOOQ库的用户,也是一个喜欢泛型谜题的好奇的Java开发人员,但是当我必须在我自己的界面中放入通配符时,我经常会想念SQL元组的简单性Result<T>
中类型参数的类型以及处理记录类型的接口时产生的麻烦 - 而不是“jOOQ故障”