lambda - Java 8 流和 lambda 是在骗人吗?

标签 lambda java-8 java-stream

<分区>

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

  • 易读性

也许是习惯问题,但如果您使用嵌套的 lambda,理解正在发生的事情可能会变成一场噩梦。

  • 可测试性

由于 Netbeans 的建议,我们倾向于将 for 循环更改为流的 foreach 调用,但是在可测试性方面存在微妙但非常危险的副作用。如果您的代码在 foreach block 内失败,IDE(实际上是编译器)根本不知道错误发生在哪一行,指向 block 的开头。此外,调试代码更加困难,因为我们无法控制计算和内部循环。

  • 表现

同样,IDE 总是建议将 accumulation 更改为一种 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 的建议。

最佳答案

尽管标题是点击诱饵,(“流和 lambda 是否在欺骗?”)我相信这里存在一些真正的问题。

如果您说“不要盲目接受 IDE 建议的重构”,那肯定是有道理的。如果生成的代码在某些方面比原始代码更糟糕,则可能是 NetBeans 的重构存在问题。话又说回来,IDE 不知道程序员在做什么,并且假定程序员确实知道他或她在做什么,暂时使事情变得更糟的重构不一定是错误。

关于提到的具体点,更具体地分解一下:

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

  • 编译时错误。这些错误,尤其是与类型推断相关的错误,可能会造成混淆。如果我在编写长流水线时遇到困难,通常我会将表达式分解为临时表达式。

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

  • 可调试性。这可能会令人困惑,并且可能会受到 IDE 调试器与新语言功能的早期问题的阻碍,但我不认为这是一个长期问题.例如,我已经能够使用 NetBeans 8 单步执行多行 lambda。我希望其他 IDE 的工作方式相当。

  • 性能。程序员总是需要知道他们在做什么,并且开发性能的心智模型是必要的。 Lambdas、流和并行性是 Java 8 中的新特性(在撰写本文时才几个月)需要一些时间。两个要点:1) 设置并行管道的成本很重要,它必须分摊到流元素的处理上。 2) 处理图元有点麻烦,但你必须注意,以免自动装箱和自动拆箱影响你的性能。这显然发生在这里。

  • 基准测试。使用真实的线束,例如 JMH而不是自己滚动。顺便说一下,Aleksey Shipilev (JMH 作者)在 JVM Language Summit 上发言昨天关于基准测试,特别是关于 pitfalls of using nanoTime 来测量耗时。使用 nanoTime 可能会遇到什么问题,您会大吃一惊。 .

最后,我不得不说,这是一个非常糟糕的例子。它肯定会使并行流和 lambda 的性能看起来很糟糕,但是 dkatzel (+1) 在那件事上有所作为。总的来说,代码有很多问题。将随机值添加到 Collection<Number> 中然后提取 double值(value)观?与实际计算相比,这更像是装箱/拆箱的度量。就代码得出明智的结论首先是困难的,但如果有问题的代码一开始就很糟糕,那么结论就没有可信度。虽然求和数字是一个可疑的基准,但合理的方法是从大量 double 开始。原语并比较传统代码和基于流的代码的代码和性能。然而,这将不得不等待另一个时间。

关于lambda - Java 8 流和 lambda 是在骗人吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25061739/

相关文章:

Action Streams 中的 Java 8 明确说明

java-8 - Java 8 流和 RxJava 可观察量之间的区别

java - 生成无限并行流

参数化类的java流返回数组

c++ - 使用 std::find_if 和 std::vector 查找大于某个值的最小元素

c++ - 对象存储 lambda 函数是否有自己的地址?

c++ - 在忽略默认参数的情况下从 lambda 函数中的模板参数调用静态函数

Java Stream Collect - 如何推断类型?

Java 8 方法引用 : provide a Supplier capable of supplying a parameterized result

Java 8 mapToInt 和 toIntFunction 示例