用Java8流取代传统的newForLoop
最后,我从Java6到Java8有了一个相对较大的飞跃,我阅读了大量Java8StreamsAPI。不幸的是,几乎所有被问到的例子几乎都接近于我想弄明白的方法,但还不够接近 我所拥有的是用Java8流取代传统的newForLoop,java,foreach,lambda,java-8,java-stream,Java,Foreach,Lambda,Java 8,Java Stream,最后,我从Java6到Java8有了一个相对较大的飞跃,我阅读了大量Java8StreamsAPI。不幸的是,几乎所有被问到的例子几乎都接近于我想弄明白的方法,但还不够接近 我所拥有的是 final List<Function<? super Double, Double>> myList = generateList(); final double myVal = calculate(10); private double calculate(double val)
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;
}
final List是的,您可以通过执行减少操作来使用流解决方案:
缩减将每个元素合并为一个值。有3种风格的reduce()
方法-这里使用的方法就可以做到这一点
一些测试代码:
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));
}
这个特定的示例对于Java8流来说是非常有问题的。它们是为顺序不重要的操作而设计的
函数应用程序不是关联的。为了解释,让我们举一个更简单的例子,在这个例子中,我们想取一个数字,并用一个数字表把它除以:< /P>
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 )
基于reduce/collector的流操作要求“reduce”操作保持关联。这是因为他们希望允许操作并行化,这样一些线程将执行一些操作,然后可以组合结果。现在,如果我们的运算符是乘法而不是除法,这不会是个问题,因为
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
的方法。但是,报告警告您不要这样做:
对于像+
这样的操作,标识为0。对于*
,标识为1。因此,使用val
作为标识是违反文档的。第二个条件问题更大
尽管非并行流的当前实现
没有使用合并器部分,这使得这两个条件都不需要,但这是未记录的,未来的实现或不同的JRE实现可能会决定创建中间结果,并使用合并器将其合并,可能是为了提高性能或出于任何其他考虑
因此,尽管存在诱惑,但不应使用Stream.reduce
来尝试模仿原始的顺序处理
有一种方法可以做到这一点,它实际上不会破坏文档。它涉及到保留一个保存结果的可变对象(它必须是一个对象,以便在仍然可变的情况下它是有效的最终对象),并使用,如果流是有序的,这将确保操作将按照它们在流中出现的顺序执行。列表的流具有定义的顺序。即使使用myList.stream().parallel()
,也可以使用此功能
就我个人而言,我发现您原来的循环比这个更可读,所以在这里使用流确实没有什么好处
根据@Tagir Valeev的评论,计划在未来的Java版本中进行操作。当这种情况发生时,它可能看起来更加优雅。您可以使用流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));
}
静态列表我不认为它做同样的事情。最初,对于三个函数f,g,h,会给出f(val)+g(f(val))+h(f(val)+g(f(val)),而这个减缩器允许将部分结果与+
结合起来,因此可以得到f(val)+g(val)+h(val)或类似的结果。这里的组合器不符合文档中的约束。我只是进行了逻辑测试,这就是代码真正的功能。10+(10+4)=24然后24+(24*2)=72然后72+(72-3)=141最终结果仍然相同。现在,我真的很好奇在这种情况下使用流与传统方法相比的性能成本。实际上,这是(希望)即将到来的工作。这里的操作是非关联的,所以reduce只会并行产生不正确的结果。就是这样,不是吗?合并器从未被调用的事实是没有记录的,这是一个不应该依赖的实现细节,这是故意的,应该避免对同一流的并行和非并行版本产生不相同结果的任何使用。如果代码依赖于从未被调用的合并器,它至少应该提供(x,y)->{throw new AssertionError();}
作为组合器,而不是默默地生成错误值的东西@gabizou:您的性能测试缺少任何预热阶段,因此您正在测量流框架的类加载和初始化时间。不,不是真的。Stream
操作对于像您正在做的那样固有的顺序操作并不真正起作用。我理解,我只是希望流最终会有相同的性能。在大范围内,很可能对于大型函数集,这可能会以奇偶性结束,但对于我所做的,这是不可能的。当然,随着新Java版本的开发,我们可能会看到优化和增强
(a × 3.5 × 7.0 ) × (0.5 × 19)
result = val + f(val) + g(val + f(val)) + h(val + f(val) + g(val + f(val)))
<U> U reduce(U identity,
BiFunction<U,? super T,U> accumulator,
BinaryOperator<U> combiner)
combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
public static double streamedCalculate(double val) {
class MutableDouble {
double currVal;
MutableDouble(double initVal) {
currVal = initVal;
}
}
final MutableDouble accumulator = new MutableDouble(val);
myList.stream().forEachOrdered((x) -> accumulator.currVal += x.apply(accumulator.currVal));
return accumulator.currVal;
}
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));
}