我已经使用Java 8的lambdas和流了一段时间,因为我的硕士学位项目,我注意到一些在互联网上没有广泛讨论过的东西。我使用Netbeans进行开发,很多时候它建议改变老式的#34;风格有利于这两个新的构造函数。但我想知道这些建议是否真的有用。要点是:
也许是习惯问题,但如果你使用嵌套的lambdas,那么理解正在发生的事情就变成了一场噩梦。
由于Netbeans的建议,我们倾向于将for循环更改为流的foreach调用,但是在可测试性方面存在微妙但非常危险的副作用。如果你的代码在foreach块中失败,那么IDE(实际上是编译器)根本不知道错误发生在哪一行,指向块的开始。此外,调试代码更加困难,因为我们无法控制计算和内部循环。
同样,IDE总是建议将累积更改为一种map reduce算法。后者看起来更复杂,所以我创建了一个简单的测试来检查这种方法有多好。令人惊讶的是,慢了!
代码:
public class Java8Kata {
public static void main(String[] args) {
System.out.println("Generating random numbers...");
final Collection<Number> numbers = getRandomNumbers();
System.out.println("Starting comparison...");
for (int i = 0; i < 20; i++) {
getTotalConventionalStyle(numbers);
getTotalNewStyle(numbers);
}
}
public static void getTotalConventionalStyle(Collection<Number> numbers) {
long startTime = System.nanoTime();
System.out.println("\n\nstarting conventional...");
double total = 0;
for (Number number : numbers) {
total += number.doubleValue();
}
System.out.println("total = " + total);
System.out.println("finish conventional:" + getPeriod(startTime) + " seconds");
}
public static void getTotalNewStyle(Collection<Number> numbers) {
long startTime = System.nanoTime();
System.out.println("\n\nstarting new style ...");
double total = 0;
//netbeans conversion
total = numbers.parallelStream().map((number) -> number.doubleValue()).reduce(total, (accumulator, _item) -> accumulator + _item);
System.out.println("total = " + total);
System.out.println("finish new style:" + getPeriod(startTime) + " seconds");
}
public static Collection<Number> getRandomNumbers() {
Collection<Number> numbers = new ArrayList<>();
for (long i = 0; i < 9999999; i++) {
double randomInt = 9999999.0 * Math.random();
numbers.add(randomInt);
}
return numbers;
}
public static String getPeriod(long startTime) {
long time = System.nanoTime() - startTime;
final double seconds = ((double) time / 1000000000);
return new DecimalFormat("#.##########").format(seconds);
}
}
为了确保结果一致,我进行了20次比较。
他们是:
Generating random numbers... Starting comparison... starting conventional... total = 5.000187629072326E13 finish conventional:0.309586459 seconds starting new style ... total = 5.000187629073409E13 finish new style:20.862798586 seconds starting conventional... total = 5.000187629072326E13 finish conventional:0.316218488 seconds starting new style ... total = 5.000187629073409E13 finish new style:20.594838025 seconds [...]
我的目标不是进行深度的性能测试,我只想看看Netbeans是否在帮助我。
作为结论,我可以说你应该根据自己的决定仔细使用这些新结构,而不是遵循IDE的建议。
答案 0 :(得分:5)
尽管有点击率标题,(&#34;是溪流和lambdas欺骗?&#34;)我相信这里有一些真正的问题。
如果你说'不要盲目接受IDE提出的重构&#34;那肯定是有道理的。可能是NetBeans存在问题&#39;如果结果代码在某些方面比原始代码更糟,则重构。再说一遍,IDE并不知道程序员正在做什么,并假设程序员 知道他或她在做什么,暂时让事情变得更糟的重构不是必然的一个错误。
在提到的具体问题上,更具体地分解了一下:
易读性。是的,lambdas和溪流会让事情变得更糟。但他们也可以做得更好,更好。可以使用任何语言和库构造编写错误的代码。
编译时错误。这些错误,尤其是与类型推断相关的错误,可能会造成混淆。如果我在编写长管道时遇到问题,通常我会将表达式分解为临时表达。
可测试性。在某些结构中嵌套的任何大量代码都很难测试。这包括长的多行lambda,我为此避免了这种情况。提取方法在这里非常有用。一种新兴的风格似乎更倾向于由非常简单的lambda或方法引用组成的流管道。
可调试性。这可能令人困惑,并且可能受到IDE早期问题的阻碍。调试器与新语言功能,但我不认为这是一个长期问题。例如,我已经能够使用NetBeans 8单步执行多行lambdas。我希望其他IDE可以比较有效。
表现。程序员始终需要知道自己在做什么,并且开发绩效的心理模型是必要的。 Lambda,流和并行性是Java 8中的新功能(在撰写本文时只有几个月)需要一些时间。两个快速点:1)建立并行管道的成本很重要,并且必须在流元素的处理上分摊。 2)处理原语是有点麻烦,但你必须注意,以免自动装箱和自动拆箱杀死你的表现。这显然是在这里。
基准测试。使用JMH之类的真实线束,而不是滚动自己的线束。顺便提一下,Aleksey Shipilev(JMH作者)昨天在JVM Language Summit基准测试时发表了讲话,特别是在pitfalls of using nanoTime
上测量了经过的时间。您会对使用nanoTime
可能遇到的问题感到震惊。
最后,我不得不说,这是一个非常糟糕的例子。它肯定会使并行流和lambda的性能看起来很糟糕,但是dkatzel(+1)在那个时候已经开始了。总体而言,代码存在大量问题。将随机值添加到Collection<Number>
,然后提取double
值?这更像是对装箱/拆箱的衡量,而不是真正的计算。一开始就很难得出关于代码的合理结论,但如果所讨论的代码开头不好,那么结论就没有可信度。虽然求和数是一个可疑的基准,但合理的方法是从大量double
基元开始,并比较传统和基于流的代码的代码和性能。然而,那将不得不等待另一次。
答案 1 :(得分:4)
你没有做正确的新风格总结
你想要这个:
total = numbers.parallelStream()
.mapToDouble(number -> number.doubleValue())
.sum();
这会让你Stream<Double>
变为DoubleStream
(有点像Stream<double>
),然后使用新的sum()
缩小,这是一个原始求和,不是对象求和,计算时间要快得多。
这也更容易阅读。
当我通过这个简单的代码更改在我的机器上运行时,我得到了这个:
Generating random numbers...
Starting comparison...
finish conventional: 0.078106 seconds
finish new style: 0.279964 seconds
finish conventional: 0.126721 seconds
finish new style: 0.045977 seconds
.... etc
这比您的方法快100倍,基本上和传统方法一样快。运行新流API会对性能产生影响。考虑运行多线程迭代和求和所需的所有后台工作。