Java8 Lambda表达式和流操作如何让你的代码变慢5倍

原文出处:ImportNew

有许许多多关于 Java 8 中流效率的讨论,但根据 Alex Zhitnitsky 的测试结果显示:坚持使用传统的 Java 编程风格——iterator 和 for-each 循环——比 Java 8 的实现性能更佳。

Java 8 中的 Lambda 表达式和流(Stream)受到了热烈欢迎。这是 Java 迄今为止最令人激动的特征。这些新的语言特征允许采用函数式风格来进行编码,我们可以用这些特性完成许多有趣的功能。这些特性如此有趣以至于被认为是不合理的。我们对此表示怀疑,于是决定对这些特性进行测试。

我们创建一个简单的任务:从一个 ArrayList 找出最大值,将传统方式与 Java 8 中的新方式进行测试比较。说实话,测试的结果让我感到非常惊讶。

命令式风格与 Java 8 函数式编程风格比较

我喜欢直接进入主题,所以先看一下结果。为了做这次基准测试,我们先创建了一个 ArrayList,并插入一个 100000 个随机整数,并通过 7 种不同的方式遍历所有的值来查找最大值。实现分为两组:Java 8 中引入的函数式风格与 Java 一直使用的命令式风格。

这是每个方法耗费的时长:

最大错误记录是并行流上的 0.042,完整输出结果在这篇文章结尾部分可以看到。

小贴士:

哇哦!Java 8 中提供的任何一种新方式都会产生约 5 倍的性能差异。有时使用简单迭代器循环比混合 lambda 表达式和流更有效,即便这样需要多写几行代码,且需要跳过甜蜜的语法糖(syntactic suger)。

使用迭代器或 for-each 循环是遍历 ArrayList 最有效的方式,性能比采用索引值的传统 for 循环方式好两倍。

在 Java 8 的方法中,并行流的性能最佳。但是请小心,在某些情况下它也可能会导致程序运行得更慢。

Lambda 表达式的速度介于流与并行流之间。这个结果确实挺令人惊讶的,因为 lambda 表达式的实现方式是基于流的 API 来实现的。

不是所有的情况都如上所示:当我们想演示在 lambda 表达式和流中很容易犯错时,我们收到了很多社区的反馈,要求我们优化基准测试代码,如消除整数的自动装包和解包操作。第二次测试(已优化)的结果在这篇文章结束位置可以看到。

让我们快速看一下每个方法,按照运行速度由快到慢:

命令式风格

iteratorMaxInteger()——使用迭代器遍历列表:


1

2

3

4

5

6

7

public int iteratorMaxInteger() {

    int max = Integer.MIN_VALUE;

    for (Iterator it = integers.iterator(); it.hasNext(); ) {

        max = Integer.max(max, it.next());

    }

    return max;

}

forEachLoopMaxInteger()——不使用迭代器,使用 For-Each 循环遍历列表(不要误用 Java 8 的 forEach)


1

2

3

4

5

6

7

public int forEachLoopMaxInteger() {

    int max = Integer.MIN_VALUE;

    for (Integer n : integers) {

        max = Integer.max(max, n);

    }

    return max;

}

forMaxInteger()——使用简单的 for 循环和索引遍历列表:


1

2

3

4

5

6

7

public int forMaxInteger() {

    int max = Integer.MIN_VALUE;

    for (int i = 0; i < size; i++) {

        max = Integer.max(max, integers.get(i));

    }

    return max;

}

函数式风格

parallelStreamMaxInteger()——使用 Java 8 并行流遍历列表:


1

2

3

4

public int parallelStreamMaxInteger() {

    Optional max = integers.parallelStream().reduce(Integer::max);

    return max.get();

}

lambdaMaxInteger()——使用 lambda 表达式及流遍历列表。优雅的一行代码:


1

2

3

public int lambdaMaxInteger() {

    return integers.stream().reduce(Integer.MIN_VALUE, (a, b) -> Integer.max(a, b));

}

forEachLambdaMaxInteger()——这个用例有点混乱。可能是因为 Java 8 的 forEach 特性有一个很烦人的东西:只能使用 final 变量,所以我们创建一个 final 包装类来解决该问题,这样我们就能访问到更新后的最大值。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public int forEachLambdaMaxInteger() {

    final Wrapper wrapper = new Wrapper();

    wrapper.inner = Integer.MIN_VALUE;

    integers.forEach(i -> helper(i, wrapper));

    return wrapper.inner.intValue();

}

public static class Wrapper {

    public Integer inner;

}

private int helper(int i, Wrapper wrapper) {

    wrapper.inner = Math.max(i, wrapper.inner);

    return wrapper.inner;

}

顺便提一下,如果要讨论 forEach,我们提供了一些有趣的关于它的缺点的见解,答案参见StackOverflow

streamMaxInteger()——使用 Java 8 的流遍历列表:


1

2

3

4

public int streamMaxInteger() {

    Optional max = integers.stream().reduce(Integer::max);

    return max.get();

}

优化后的基准测试

根据这篇文章的反馈,我们创建另一个版本的基准测试。源代码的不同之处可以在这里查看。下面是测试结果:

最后的思考

开始使用 Java 8 的第一件事情是在实践中使用 lambda 表达式和流。但是请记住:它确实非常好,好到可能会让你上瘾!但是,我们也看到了,使用传统迭代器和 for-each 循环的 Java 编程风格比 Java 8 中的新方式性能高很多。

当然,这也不是绝对的。但这确实是一个相当常见的例子,它显示可能会有大约 5 倍的性能差距。如果这影响到系统的核心功能或成为系统一个新的瓶颈,那就相当可怕了。

时间: 2024-11-02 07:04:13

Java8 Lambda表达式和流操作如何让你的代码变慢5倍的相关文章

Java8 Lambda表达式详解手册及实例

先贩卖一下焦虑,Java8发于2014年3月18日,距离现在已经快6年了,如果你对Java8的新特性还没有应用,甚至还一无所知,那你真得关注公众号"程序新视界",好好系列的学习一下Java8的新特性.Lambda表达式已经在新框架中普通使用了,如果你对Lambda还一无所知,真得认真学习一下本篇文章了. 现在进入正题Java8的Lambda,首先看一下发音 ([?l?md?])表达式.注意该词的发音,b是不发音的,da发[d?]音. 为什么要引入Lambda表达式 简单的来说,引入La

Java8 Lambda表达式应用案例 -- 单线程游戏服务器+异步数据库操作

前段时间我们游戏服务器的开发环境升级到了Java8,这两天我又把服务器的线程模型重新设计了一下,用上了Lambda表达式.Lambda表达式确实能够大幅简化Java代码,特别是丑陋不堪的匿名内部类,这篇文章主要就是想和大家分享这一点. 线程模型 首先简单介绍一下我们游戏服务器的线程模型,大致如下图所示: Netty线程池只处理消息的收发,当Netty收到消息之后,会交给游戏逻辑线程处理.由于是单线程在处理游戏逻辑,所以每一个消息必须很快处理完,也就是说,不能有数据库等耗时操作,不然逻辑线程很可能

JAVA8 Lambda表达式完全解析

JAVA8 新特性 在学习JAVA8 Lambda之前,必须先了解一下JAVA8中与Lambda相关的新特性,不然对于一些概念会感到比较陌生. 1. 接口的默认方法和静态方法 Java 8允许我们给接口添加一个默认方法,用default修饰即可.默认方法可以重写,也可以不用重写.这就是和抽象方法的区别,在用法上,没有其他区别. public interface IMyInterface { void onMethond(String str);//这是一个抽象方法 default String

【Java学习笔记之三十一】详解Java8 lambda表达式

Java 8 发布日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Java 8之前,如果想将行为传入函数,仅有的选择就是匿名类,需要6行代码.而定义行为最重要的那行代码,却混在中间不够突出.Lambda表达式取代了匿名类,取消了模板,允许用函数式风格编写代码.这样有时可读性更好,表达更清晰.在Java生态系统中,函数式表达与对面向对象的全面支持是个激动人心的进步.将进一步促进并行

Java8 lambda表达式10个示例

本文由 ImportNew Java 8 刚于几周前发布,日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Java 8之前,如果想将行为传入函数,仅有的选择就是匿名类,需要6行代码.而定义行为最重要的那行代码,却混在中间不够突出.Lambda表达式取代了匿名类,取消了模板,允许用函数式风格编写代码.这样有时可读性更好,表达更清晰.在Java生态系统中,函数式表达与对面向对象的全面

java8 Lambda表达式的10个例子(转)

原文:http://jobar.iteye.com/blog/2023477 Java8中Lambda表达式的10个例子 例1 用Lambda表达式实现Runnable接口 Java代码 收藏代码//Before Java 8: new Thread(new Runnable() { @Override public void run() { System.out.println("Before Java8, too much code for too little to do");

Java8 lambda表达式10个示例&lt;转&gt;

例1.用lambda表达式实现Runnable 我开始使用Java 8时,首先做的就是使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例.看一下Java 8之前的runnable实现方法,需要4行代码,而使用lambda表达式只需要一行代码.我们在这里做了什么呢?那就是用() -> {}代码块替代了整个匿名类. // Java 8之前: new Thread(new Runnable() { @Override public void run() { System.o

Java8 lambda 表达式示例

1.1 用lambda表达式实现Runnable 使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例.看一下Java 8之前的runnable实现方法,需要4行代码,而使用lambda表达式只需要一行代码.我们在这里做了什么呢?那就是用() -> {}代码块替代了整个匿名类. // Java 8之前: new Thread(new Runnable() { @Override public void run() { System.out.println("Bef

深入学习Java8 Lambda表达式 -- 从函数式接口说起

希望本文能够成为Java8 Lambda表达式的快速入门指南. 函数式接口 理解Functional Interface(函数式接口,以下简称FI)是学习Java8 Lambda表达式的关键所在,所以放在最开始讨论.FI的定义其实很简单:任何接口,如果只包含唯一一个抽象方法,那么它就是一个FI.为了让编译器帮助我们确保一个接口满足FI的要求(也就是说有且仅有一个抽象方法),Java8提供了@FunctionalInterface注解.举个简单的例子,Runnable接口就是一个FI,下面是它的源