虽然Java 8已经出了好几年了,但是很多朋友可能对于其中的一些特性还是不太了解。甚至对lambda表达式这个特性可能会产生误解,误认为lambda表达式会影响程序的速度。其中也不乏很多误人子弟的自媒体传播这些错误的观点。

今天我看到一篇自媒体推送的文章,号称用Java字节码分析为什么lambda表达式速度慢,但是其中漏洞百出,搞得我忍不住写了这么一篇文章,为一些受到误导的朋友纠正一个概念:lambda表达式和普通的循环一样,不会影响到程序速度,大家可以放心使用。

因为头条压缩图片的缘故,所以对于小段代码,我用高亮代码图片的形式贴出。对于大段代码,直接贴代码,可能会影响大家的阅读体验。也希望头条能够允许上传高清图片,让大家的阅读体验更好一下。

lambda表达式是什么

可能还有一些朋友对lambda表达式还是不太清楚,所以我先介绍一下lambda表达式的概念。简单来说lambda表达式就是匿名函数,在一些支持匿名函数的语言中,用不用lambda表达式其实不是那么重要。但是因为Java不支持匿名函数,所以lambda表达式可以极大的简化这些场合的代码。

先来看看一个例子。假如我们需要在一个新线程中运行代码,可能需要创建一个新的Runnable对象。此处使用了Java的一项特性匿名内部类,创建了一个新的临时的Runnable对象。但是代码如你所见非常难看,大段的缩进和方括号,非常影响阅读。

如果换成了lambda表达式的实现,如你所见,代码非常干净整洁。

这种形如(a,b)->{ ..... }的表达式就是lambda表达式。上面已经提到过了,lambda表达式其实就是匿名函数,箭头前面的括号内部的就是函数的参数列表;箭头后面的括号内部的就是方法体,假如方法体只有一行语句或者表达式,方法体的括号可以省略。

lambda表达式参数的类型也不需要写明,编译器会自动从前面的类型中推断。在上面的例子中,因为Runnable中的run函数没有参数,所以lambda表达式自然也不需要参数。你可能会想到,假如类型中有多个函数怎么办呢?这时候编译器无法推断,程序就会报错。这也是Java lambda表达式的一个限制,前面的接口类型中只能有一个函数声明。

很多古板的程序员不喜欢这个特性,认为它会影响到程序的可读性。但是实际情况恰恰相反,合理的利用lambda表达式,不仅不会污染代码的可读性,反而会大大加强可读性。lambda表达式这个特性,已经被现在很多新的编程语言吸收,足见其流行程度。

错误的测试方法

很多朋友可能对lambda表达式的运行速度产生疑问,会不会用了这种写法,程序的运行速度就会变慢呢?这种担心也是完全多余的,Java作为一门经典的企业级应用开发语言,Oracle对每个新添加的特性都是小心翼翼的。既然这个特性被添加到Java语言中,那么足以说明Oracle对其进行了深刻的优化,运行速度绝对是有保证的,就算比普通循环慢一点,也不会慢到哪里去。

可能有些人用了错误的测试方法对lambda表达式进行了测试,发现速度不如普通的for循环,然后就得出结论:lambda表达式运行速度慢。这种测试是完全不负责任的。下面的代码就是一种错误的测试方法,测试结果:lambda表达式用时150毫秒,而普通循环用时7毫秒。因此得出结论:lambda表达式慢。大家可以看看代码,然后想想问题在哪里。

public class LambdaTest {  public static int N = 1_0000_0000;  static List list = IntStream.range(0, N).boxed().collect(Collectors.toList());  public static void main(String[] args) {    long start, end;    start = System.currentTimeMillis();    lambdaTest();    end = System.currentTimeMillis();    System.out.println("lambda:" + (end - start));    start = System.currentTimeMillis();    loopTest();    end = System.currentTimeMillis();    System.out.println("loop:" + (end - start));  }  static void lambdaTest() {    list.forEach(i -> {    });  }  static void loopTest() {    for (int i = 0; i < list.size(); i++) {    }  }}

好了不卖关子了,直接说结论吧。上面测试方法的问题在于,两种测试方法实际上根本不对等。lambda表达式的测试中,虽然方法体是空的,但是程序执行的时候,仍然会取出每一个元素,然后再应用空的方法。而循环测试中,真的只是执行了一个空循环,什么也没干。因此这种方法测出来的结论,完全不能证明lambda表达式比空循环慢。

公平的测试方法应该是怎么样的呢?对于循环,一样要加上取元素和应用空方法的操作。为此在空循环中增加了一部分代码。这样测出来的结果,lambda表达式和普通循环一样都是150毫秒左右,存在几毫秒的误差。这次的结果可以反映真实情况了,那就是两者没有什么速度差别。大家可以自己运行代码试试。

更加实际的测试

不管怎么说,用空的方法来测试lambda表达式和普通循环并不具有实际意义。所以我换了一种更加实际的方法,来看看lambda相较于普通的循环有没有优势所在。

首先准备一个用户类,这里用到了lombok自动生成各种工具方法,为我们节约时间。

然后准备一个随机类,准备用来生成10万个随机用户,来进行下一步的操作。

接下来就是测试代码了。测试代码其实也很简单,随机生成一千万个用户,然后进行简单的筛选操作,选出来所有ID大于1000且为偶数,用户名以字母a开头的用户。两种测试结果输出各自的筛选结果数量,以保证结果是相同的。因为这次的测试比较复杂,所以可以看出实际的差异。在我的电脑上,lambda表达式耗时100毫秒左右,而循环耗时80毫秒左右。可见lambda表达式虽然比循环慢一点,但是差距很小,在千万次循环的级别仅差几十毫秒,对程序的运行基本没有什么影响。

public class LambdaTest {  public static int N = 1000_0000;  static List list;  public static void main(String[] args) {    init();    long start, end;    start = System.currentTimeMillis();    lambdaTest();    end = System.currentTimeMillis();    System.out.println("lambda:" + (end - start));    start = System.currentTimeMillis();    loopTest();    end = System.currentTimeMillis();    System.out.println("loop:" + (end - start));  }  static void init() {    list = new ArrayList<>();    for (int i = 0; i < N; i++) {      list.add(new User(MyRandom.randomId(), MyRandom.randomUsername()));    }  }  static void lambdaTest() {    List r = list.stream()            .filter(e -> e.getName().startsWith("a"))            .filter(e -> e.getId() % 2 == 0)            .filter(e -> e.getId() > 1000)            .collect(Collectors.toList());    System.out.println(r.size());  }  static void loopTest() {    List r = new ArrayList<>();    for (User user : list) {      if (user.getName().startsWith("a") && user.getId() % 2 == 0 && user.getId() > 1000) {        r.add(user);      }    }    System.out.println(r.size());  }}

这次的测试算是一个比较实际的测试了,生成一千万个用户并对其属性进行检查,过滤出符合条件的用户。测试的数量是一千万,但是测试结果相差并不大。可见其实lambda表达式并不怎么影响程序的运行速度。值得注意的是,这个测试数据完全是保存在内存上的,而一般情况下数据都是从数据库中加载出来的。这时候程序的瓶颈在数据库的IO上,就算程序本身速度相差几十毫秒,相较于数据库的延迟完全可以忽略不计。

我们的原则是不进行过早的优化。写程序的时候,该怎么写就怎么写,lambda这种好用的新特性,该用的时候就应该用,不要害怕它影响性能。等到程序写完,需要优化的时候,老老实实的跑profile,查看程序的瓶颈究竟在哪里。一般情况下程序问题都在数据库IO、算法不够高效或者是内存泄露上,我还真没听说过哪个程序写的非常完美,就是被lambda表达式的速度拖后腿的。实际上,虽然很多程序员都担心lambda表达式的速度,但是他们的程序完全优秀到需要扣lambda表达式细节的这种程度。

反过来说适当的时候应用这些新特性,反而会增加代码的可读性。就拿上面这个例子来说,通过三次filter方法过滤程序,最后用collect方法得到结果,这种流式函数调用不仅非常简单易读,而且十分优雅。反观循环版本中的查找操作,只能通过if判断简单粗暴的进行。这还是一个简单的例子,假如查找操作比较复杂,带了十几个查询条件的话,那么循环版本的代码就会变成可读性的灾难。

这里还有一个细节值得注意。为了最高效的运行,循环版本的代码只能在一个if中不断的增加判断条件。而lambda表达式版本则是流式调用了三次filter语句,但是它们的运行结果相差不大。相信你应该也猜到原因了:lambda表达式和流类库内部做了特殊的优化,就算是多个过滤条件,也会保证仅仅循环一次。因此放心大胆的使用lambda表达式吧!它是编写代码的利器!

lambda表达式,更加强大

写到这里,本文的内容应该是差不多了。但是我猜很多朋友看了以后,会说“你说了这么多,lambda表达式不还是比循环慢嘛。说来说去,我还是要继续用循环”。在这里我想说明一下,我的观点是:lambda表达式虽然比循环慢那么一点点,但是带来的便利性和优化空间,远远不是普通循环可以比拟的。

上面的例子用了一千万次的循环,才得到了几十毫秒的差距。而实际情况中,几千次或者几万次的循环,差距便会忽略不计。而且如果加上数据库等外部数据源的读写延迟,程序的这点运行速度完全就不值一提了。所有担心lambda表达式的朋友基本都是杞人忧天。而lambda表达式带来的方便确实实实在在的。更重要的是,普通循环的优化非常困难,基本要重写整个代码,在这之中很容易发生错误。而lambda表达式的优化则简单许多。

上面的例子恰好是一个适合并行化的例子,优化方法很简单,多加一行parallel()方法调用即可。并行化是另外一个非常复杂的主题,但是在这个例子中,第一数据量大(一千万之多),第二数据易于分割和和合并(ArrayList可以用下标直接定位中间的元素),第三操作都是只读的(不会影响到数据集本身),所以正好适合并行化。并行化之后,lambda表达式的运行速度已经和循环相差无几了(仅差几毫秒左右)。而普通代码的并行化,我想这就不是一般程序员可以轻松写出来的东西了。

好了,本文终于写完了,其实本来准备反驳一下错误观点就结束的,结果不知不觉写了这么多。如果大家觉得本文不错的话,欢迎点赞、评论、转发,创作不易,还请大家多多支持!在此先谢谢各位了。

lambda函数if_lambda表达式速度如何呢?看完这篇文章你就明白了相关推荐

  1. 商业智能BI的前景如何?看完这篇文章你就明白了

    近日,IDC国际数据公司发布了<2021下半年中国商业智能软件市场跟踪报告>,其中提到的关于商业智能BI的市场数据就是现阶段商业智能BI情况的最好呈现,具体表现为2021年下半年中国的商业 ...

  2. 什么软件可以分割音频?看完这篇文章你就明白了

    大家可能对于音频分割不太了解,除了帮助完善电影语音,分割音频技术还可以应用于其他的场景中.例如,在语言学研究中,分割音频技术可以用来分析不同的语音特征和语音变化,从而更好地理解人类语言的本质和特点,或 ...

  3. 抖音如何推动音乐的流行?看完这篇文章你就明白了

    抖音是一款以音乐为主要内容的短视频分享平台.它的出现不仅让年轻人的生活更加丰富多彩,还对音乐产业带来了前所未有的影响,不若与众就来聊聊抖音对音乐几个方面的影响. 一.推动音乐的流行 抖音上有许多热门音 ...

  4. 爬虫推特数据分析的外文文献_什么是网络爬虫?有什么用?怎么爬?看完这篇文章你就明白了...

    源:Python架构师 https://dwz.cn/LI7NNc4g 一.什么是网络爬虫 随着大数据时代的来临,网络爬虫在互联网中的地位将越来越重要.互联网中的数据是海量的,如何自动高效地获取互联网 ...

  5. raw分区怎么恢复?看完这篇文章你就明白了!

    有的时候,我们在使用移动硬盘或是U盘这类存储设备时,发现突然打不开,在资源文件管理器里访问这个盘时会遇到类似下面这些错误提示,例如: 1.磁盘未格式化.驱动器G: 中的磁盘未被格式化.想现在格式化吗? ...

  6. IaaS、PaaS、SaaS、DaaS都是什么?现在怎么样了?看完这篇文章你就明白了!

    导读:本文将详细科普云计算的概念.云服务的发展现状,并逐一介绍各种云服务模式(IaaS.PaaS.SaaS.DaaS),建议收藏! 01 云计算的概念 云是一种服务,可以像使用水.电.煤那样按需使用. ...

  7. Dart语言基础,看完这篇文章就够了(二)

    文章内容是我在学习Flutter过程中对知识点的梳理和总结.如有不对的地方,欢迎指出. 本文承接Dart语言基础,看完这篇文章就够了(一),进一步了解Dart语法知识. 文章目录 1 流程控制语句 2 ...

  8. 看完这篇文章,你的Python基础就差不多了(附571集精品教程)

    学一门语言贵在坚持用它,不用就淡忘了,而记录下一篇文章也有助于日后快速回忆.全文分为两大部分,分别是Python基础语法和面向对象. 入门Python其实很容易,但是我们要去坚持学习,每一天坚持很困难 ...

  9. python装饰器原理-看完这篇文章还不懂Python装饰器?

    原标题:看完这篇文章还不懂Python装饰器? 1.必备 2.需求来了 初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作.redis调用.监控API等功能.业务部门 ...

最新文章

  1. Swift实战-QQ在线音乐(第二版)
  2. 大道至简第一章伪代码读后感
  3. 2019诺贝尔生理学\医学奖率先颁出!英美3学者加冕,揭秘血与氧关系,抗击肿瘤和癌症...
  4. D3.tsv与D3.csv加载数据
  5. Using the command line to manage files on HDFS--转载
  6. [转]查看事物码相关的数据对象
  7. 北京规定6类高级人才来京最高奖励30万元
  8. 1020:打印ASCII码
  9. C# Memory Cache 踩坑记录
  10. 评一本书:C#.net 手机动漫游戏设计教程 (一)
  11. visual stdio打开之后与屏幕尺寸不匹配_柔和点亮桌面,让眼睛更舒服,雷神屏幕挂灯L1体验...
  12. java dozer map转对象_对象转换利器之Dozer
  13. iOS-代码实现TableViewCell创建多个样式的Cell
  14. 网页设计\网页制作常用软件大全
  15. A站、B站、C站、D站、E站、F站、G站、H站、I站、J站、K站、L站、M站、N站、O站、P站、Q站、R站、S站、T站、U站、V站、W站、X站、Y站、Z站都是什么网站?Q站是什么?
  16. 有关lodop.js和CLodop服务
  17. 几个互联网项目管理软件
  18. 统计年鉴 付费下载 夏泽网
  19. 服务器系统做个备份吗,服务器操作系统能做备份吗
  20. 粉丝经济大爆发居然真的来了

热门文章

  1. 深入了解 Oracle Flex ASM 及其优点
  2. java 容器、二叉树操作、107
  3. java动态代理(JDK和cglib)
  4. tcp/udp高并发和高吐吞性能测试工具
  5. [数字图像处理]图像去噪初步(1)--均值滤波器
  6. Fedora安装Nvidia显卡驱动方法
  7. nyoj - 947(Max Xor)字典树
  8. PHP实现四位数字+字母验证码
  9. numpy常用函数(power、sum、tile、transpose等)
  10. 4.29python