所以,最后从Java 6跳到Java 8,我已经阅读了相当数量的Java 8 Streams API。不幸的是,几乎所有被问到的例子几乎都接近于我想要弄清楚怎么做,但还不够接近。
我拥有的是
final List<Function<? super Double, Double>> myList = generateList();
final double myVal = calculate(10);
private double calculate(double val) {
for (Function<? super Double, Double> function : this.myList) {
val += function.apply(val);
}
return val;
}
现在,我已经明白我可以使用.stream().forEach()
执行类似操作,但这只适用于foreach和stream需要最终变量。我尝试使用DoubleStream
进行探索以得到sum()
,但我需要将当前总和重新应用于每个Function
并将该总和添加到下一个函数,如上面的代码示例所示。
这是否可以使用纯Stream API?
编辑:因此,在使用reduce()
区域进行测试后,我对执行此类计算所花费的时间进行了简单测试,结果不支持流。这是一个例子https://gist.github.com/gabizou/33f616c08bde5ab97e56。包括来自相当基本的测试的日志输出。
答案 0 :(得分:10)
您可以使用流API从函数列表中组合函数。
static List<Function<? super Double, Double>> myList
= Arrays.asList(d -> d + 4, d -> d * 2, d -> d - 3);
static Function<Double, Double> total=myList.stream()
.map(f -> (Function<Double, Double>) d -> d + f.apply(d))
.reduce(Function::andThen).orElse(Function.identity());
static double calculate(double val) {
return total.apply(val);
}
public static void main(String[] args) {
System.out.println(calculate(10));
}
产生组合函数的流操作没有关联性问题,理论上甚至可以并行运行(尽管这里没有任何好处),而结果是一个单独的函数,它本身是顺序的并且从不会被分解成部分这需要是联想的。
答案 1 :(得分:3)
是的,您可以通过执行缩减:
来使用流解决方案private double calculate(double val) {
return myList.stream().reduce(val, (d, f) -> d + f.apply(d), (a, b) -> a + b);
}
减少需要每个元素并将其聚合(减少)为一个值。 reduce()
方法有3种风格 - 这里使用的方法就是诀窍。
一些测试代码:
static Function<? super Double, Double> a = (d) -> d + 4;
static Function<? super Double, Double> b = (d) -> d * 2;
static Function<? super Double, Double> c = (d) -> d - 3;
static List<Function<? super Double, Double>> myList = Arrays.asList(a, b, c);
static double calculate(double val) {
return myList.stream().reduce(val, (d, f) -> d + f.apply(d), (a, b) -> a + b);
}
public static void main(String[] args) {
System.out.println(calculate(10));
}
输出:
141.0
答案 2 :(得分:0)
这个特殊的例子对Java 8流来说非常有问题。它们专为订单不重要的操作而设计。
功能应用程序不是关联的。为了解释,让我们举一个更简单的例子,其中一个人想要取一个数字并将其除以一个数字列表:
static List<Double> dividers = Arrays.asList( 3.5, 7.0, 0.5, 19.0 );
public double divideByList( double a ) {
for ( Double d : dividers ) {
a /= d;
}
return a;
}
所以,你得到的是
a ÷ 3.5 ÷ 7.0 ÷ 0.5 ÷ 19.0
算术很简单 - 除法是左关联的,意味着它相当于
a ÷ ( 3.5 × 7.0 × 0.5 × 19.0)
不
a ÷ ( 3.5 ÷ 7.0 ÷ 0.5 ÷ 19.0 )
和不
( a ÷ 3.5 ÷ 7.0 ) ÷ ( 0.5 ÷ 19.0 )
流操作,即基于reduce / collectors的操作,需要&#34;减少&#34;操作将是左关联的。这是因为他们希望允许操作并行化,这样一些线程将执行一些操作,然后结果可以组合。现在,如果你的运算符是乘法而不是除法,那么这不是问题,因为
a × 3.5 × 7.0 × 0.5 × 19.0
与
相同(a × 3.5 × 7.0 ) × (0.5 × 19)
这意味着一个线程可以执行a × 3.5 × 7.0
,另一个线程可以执行0.5 × 19.0
操作,然后您可以将结果相乘并获得与顺序计算中相同的内容。但是对于分工来说,那是行不通的。
函数应用程序也是非关联的,就像除法一样。也就是说,如果您有函数f
,g
和h
,并且运行顺序计算,那么您最终会得到:
result = val + f(val) + g(val + f(val)) + h(val + f(val) + g(val + f(val)))
现在,如果你有两个中间线程,一个应用f
和g
,另一个应用h
,你想要结合结果 - 没有办法得到首先将正确的值放入h
。
正如@Bohemian建议的那样,您可能会尝试使用像Stream.reduce
这样的方法。但documentation警告你不要这样做:
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
...
标识值必须是组合器函数的标识。这意味着对于所有u,组合器(identity,u)等于u。另外,组合器功能必须与累加器功能兼容;对于所有你和你,必须具备以下条件:
combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
对于+
这样的操作,标识为0.对于*
,标识为1.因此,使用val
作为{{1}的文档是违反文档的}。第二个条件甚至更成问题。
虽然非并行流的当前实现不使用组合器部分,这使得两个条件都不需要,但未记录> strong>,以及未来的实现或不同的JRE实现,可能决定创建中间结果并使用组合器加入它们,可能是为了提高性能或任何其他考虑因素。< / p>
所以,尽管存在诱惑,但不使用 是一种方法,它实际上并没有破坏文档。它涉及保持一个保存结果的可变对象(它必须是一个对象,以便它仍然是有效的最终,同时仍然是可变的),并使用 就个人而言,我发现原始循环比这更具可读性,因此在这里使用流确实没有优势。 基于@Tagir Valeev的评论,计划在未来的Java版本中进行foldLeft操作。当发生这种情况时,它可能看起来更优雅。identity
来尝试模仿原始的顺序处理。Stream.forEachOrdered
,这保证了操作将在如果订购了流,则它们会出现在流中。并且列表的流具有已定义的顺序。即使您使用Stream.reduce
,这也可以。myList.stream().parallel()