Java8流和lambda是否具有欺骗性?

Java8流和lambda是否具有欺骗性?,lambda,java-8,java-stream,Lambda,Java 8,Java Stream,我使用Java8的lambdas和streams已经有一段时间了,因为我的硕士学位项目和我注意到一些在互联网上没有广泛讨论的事情。我正在使用Netbeans进行开发,很多时候它建议改变“老式”的风格,支持这两个新的构造函数。但我不知道这些建议是否真的有用。要点是: 易读性 也许是习惯的问题,但是如果你使用嵌套的lambda,那么理解正在发生的事情就会变成一场噩梦 可测试性 由于Netbeans的建议,我们倾向于将for循环更改为流的foreach调用,然而在可测试性方面有一个微妙但非常危

我使用Java8的lambdas和streams已经有一段时间了,因为我的硕士学位项目和我注意到一些在互联网上没有广泛讨论的事情。我正在使用Netbeans进行开发,很多时候它建议改变“老式”的风格,支持这两个新的构造函数。但我不知道这些建议是否真的有用。要点是:

  • 易读性
也许是习惯的问题,但是如果你使用嵌套的lambda,那么理解正在发生的事情就会变成一场噩梦

  • 可测试性
由于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);
}
公共类Java8Kata{
公共静态void main(字符串[]args){
System.out.println(“生成随机数…”);
最终收集编号=getRandomNumbers();
System.out.println(“开始比较…”);
对于(int i=0;i<20;i++){
GetTotalConventionStyle(数字);
getTotalNewStyle(数字);
}
}
公共静态void GetTotalConventionStyle(集合编号){
long startTime=System.nanoTime();
System.out.println(“\n\n开始常规…”);
双倍合计=0;
用于(编号:编号){
总数+=数字。doubleValue();
}
System.out.println(“total=“+total”);
System.out.println(“完成常规:“+getPeriod(startTime)+”秒”);
}
公共静态void getTotalNewStyle(集合编号){
long startTime=System.nanoTime();
System.out.println(“\n\n正在启动新样式…”);
双倍合计=0;
//netbeans转换
total=numbers.parallelStream().map((number)->number.doubleValue()).reduce(total,(累加器,_项)->累加器+_项);
System.out.println(“total=“+total”);
System.out.println(“完成新样式:“+getPeriod(startTime)+”秒”);
}
公共静态集合getRandomNumber(){
集合编号=新的ArrayList();
用于(长i=0;i<9999999;i++){
double randomInt=999999.0*Math.random();
数字。添加(随机点);
}
返回号码;
}
公共静态字符串getPeriod(长startTime){
长时间=System.nanoTime()-startTime;
最终双倍秒=((双倍)时间/100000000);
返回新的十进制格式(“秒”)。格式(秒);
}
}

我已经进行了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 [...] 正在生成随机数。。。 开始比较。。。 开始常规。。。 总计=5.000187629072326E13 完成常规:0.309586459秒 开始新的风格。。。 总计=5.000187629073409E13 完成新样式:20.862798586秒 开始常规。。。 总计=5.000187629072326E13 完成常规:0.316218488秒 开始新的风格。。。 总计=5.000187629073409E13 完成新样式:20.594838025秒 [...] 我的目标不是进行深入的性能测试,我只是想看看Netbeans是否在帮助我


作为结论,我可以说,您应该谨慎地使用这些新结构,这是您自己的决定,而不是遵循IDE的建议。

您没有进行正确的新样式求和

你想要这个:

total = numbers.parallelStream()
                .mapToDouble(number -> number.doubleValue())
                .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倍,基本上与传统方法的平均速度一样快。运行新的streams API会对性能产生影响。考虑一下运行多线程迭代和求和所需的所有后台工作。

尽管有clickbaity的标题(“流和lambdas是否存在欺骗?”),但我相信这里存在一些真正的问题

如果你说的是“不要盲目接受IDE建议的重构”,那么请确保这是有道理的。如果生成的代码在某些方面比原始代码更糟糕,那么NetBeans的重构可能存在问题。再说一次,IDE不知道程序员在做什么,并且假设程序员知道他或她在做什么,一个暂时让事情变得更糟的重构不一定是一个bug

关于提到的具体点,请更具体地细分:

  • 易读性。是的,lambdas和streams会让事情变得更糟。但它们也可以让事情变得更好。可以使用任何语言和库结构编写错误代码

  • 编译时错误。这些错误,特别是与类型推断相关的错误,可能会令人困惑。通常,如果在编写长管道时遇到困难,我会将表达式分解为临时表达式

  • 可测试性。嵌套在某个结构中的任何大块代码都很难测试。这包括长的多行lambda,我出于这个原因和其他原因避免使用它。提取方法在这里非常有用。一种新兴的样式似乎倾向于使用由非常简单的lambda或方法引用组成的流管道