Java 8里面lambda的最佳实践

Java 8已经推出一段时间了,越来越多开发人员选择升级JDK,这条热门动弹里面看出,JDK7最多,其次是6和8,这是好事!

在8 里面Lambda是最火的主题,不仅仅是因为语法的改变,更重要的是带来了函数式编程的思想,我觉得优秀的程序员,有必要学习一下函数式编程的思想以开阔思路。所以这篇文章聊聊Lambda的应用场景,性能,也会提及下不好的一面。
Java为何需要Lambda

1996年1月,Java
1.0发布了,此后计算机编程领域发生了翻天覆地的变化。商业发展需要更复杂的应用,大多数程序都跑在更强大的装备多核CPU的机器上。带有高效运行期编
译器的Java虚拟机(JVM)的出现,使得程序员将精力更多放在编写干净、易于维护的代码上,而不是思考如何将每一个CPU时钟、每一字节内存物尽其
用。

多核CPU的出现成了“房间里的大象”,无法忽视却没人愿意正视。算法中引入锁不但容易出错,而且消耗时间。人们开发了
java.util.concurrent包和很多第三方类库,试图将并发抽象化,用以帮助程序员写出在多核CPU上运行良好的程序。不幸的是,到目前为
止,我们走得还不够远。

那些类库的开发者使用Java时,发现抽象的级别还不够。处理大数据就是个很好的例子,面对大数据,Java还欠缺高效的并行操作。Java
8允许开发者编写复杂的集合处理算法,只需要简单修改一个方法,就能让代码在多核CPU上高效运行。为了编写并行处理这些大数据的类库,需要在语言层面上
修改现有的Java:增加lambda表达式。

当然,这样做是有代价的,程序员必须学习如何编写和阅读包含lambda表达式的代码,但是,这不是一桩赔本的买卖。与手写一大段复杂的、线程安全

的代码相比,学习一点新语法和一些新习惯容易很多。开发企业级应用时,好的类库和框架极大地降低了开发时间和成本,也扫清了开发易用且高效的类库的障碍。

如果你还未接触过Lambda的语法,可以看这里。
Lambda的应用场景

你有必要学习下函数式编程的概念,比如函数式编程初探,但下面我将重点放在函数式编程的实用性上,包括那些可以被大多数程序员理解和使用的技术,我们关心的如何写出好代码,而不是符合函数编程风格的代码。

1.使用() -> {} 替代匿名类

现在Runnable线程,Swing,JavaFX的事件监听器代码等,在java 8中你可以使用Lambda表达式替代丑陋的匿名类。


  1. //Before Java 8: 
  2. new Thread(new Runnable() { 
  3. @Override 
  4. public void run() { 
  5. System.out.println("Before Java8 "); 
  6. }).start(); 
  7.  
  8. //Java 8 way: 
  9. new Thread(() -> System.out.println("In Java8!")); 
  10.  
  11. // Before Java 8: 
  12. JButton show = new JButton("Show"); 
  13. show.addActionListener(new ActionListener() { 
  14. @Override 
  15. public void actionPerformed(ActionEvent e) { 
  16. System.out.println("without lambda expression is boring"); 
  17. }); 
  18.  
  19.  
  20. // Java 8 way: 
  21. show.addActionListener((e) -> { 
  22. System.out.println("Action !! Lambda expressions Rocks"); 
  23. }); 

2.使用内循环替代外循环

外循环:描述怎么干,代码里嵌套2个以上的for循环的都比较难读懂;只能顺序处理List中的元素;

内循环:描述要干什么,而不是怎么干;不一定需要顺序处理List中的元素


  1. //Prior Java 8 : 
  2. List features = Arrays.asList("Lambdas", "Default Method", 
  3. "Stream API", "Date and Time API"); 
  4. for (String feature : features) { 
  5. System.out.println(feature); 
  6.  
  7. //In Java 8: 
  8. List features = Arrays.asList("Lambdas", "Default Method", "Stream API", 
  9. "Date and Time API"); 
  10. features.forEach(n -> System.out.println(n)); 
  11.  
  12. // Even better use Method reference feature of Java 8 
  13. // method reference is denoted by :: (double colon) operator 
  14. // looks similar to score resolution operator of C++ 
  15. features.forEach(System.out::println); 
  16.  
  17. Output: 
  18. Lambdas 
  19. Default Method 
  20. Stream API 
  21. Date and Time API 

3.支持函数编程

为了支持函数编程,Java 8加入了一个新的包java.util.function,其中有一个接口java.util.function.Predicate是支持Lambda函数编程:


  1. public static void main(args[]){ 
  2. List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp"); 
  3.  
  4. System.out.println("Languages which starts with J :"); 
  5. filter(languages, (str)->str.startsWith("J")); 
  6.  
  7. System.out.println("Languages which ends with a "); 
  8. filter(languages, (str)->str.endsWith("a")); 
  9.  
  10. System.out.println("Print all languages :"); 
  11. filter(languages, (str)->true); 
  12.  
  13. System.out.println("Print no language : "); 
  14. filter(languages, (str)->false); 
  15.  
  16. System.out.println("Print language whose length greater than 4:"); 
  17. filter(languages, (str)->str.length() > 4); 
  18.  
  19. public static void filter(List names, Predicate condition) { 
  20. names.stream().filter((name) -> (condition.test(name))) 
  21. .forEach((name) -> {System.out.println(name + " "); 
  22. }); 
  23.  
  24. Output: 
  25. Languages which starts with J : 
  26. Java 
  27. Languages which ends with a 
  28. Java 
  29. Scala 
  30. Print all languages : 
  31. Java 
  32. Scala 
  33. C++ 
  34. Haskell 
  35. Lisp 
  36. Print no language : 
  37. Print language whose length greater than 4: 
  38. Scala 
  39. Haskell 

4.处理数据?用管道的方式更加简洁

Java 8里面新增的Stream API ,让集合中的数据处理起来更加方便,性能更高,可读性更好

假设一个业务场景:对于20元以上的商品,进行9折处理,最后得到这些商品的折后价格。


  1. final BigDecimal totalOfDiscountedPrices = prices.stream() 
  2. .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) 
  3. .map(price -> price.multiply(BigDecimal.valueOf(0.9))) 
  4. .reduce(BigDecimal.ZERO,BigDecimal::add); 
  5.  
  6. System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); 

想象一下:如果用面向对象处理这些数据,需要多少行?多少次循环?需要声明多少个中间变量?

关于Stream API的详细信息,可以查看我之前写的文章 。

Lambda的性能

Oracle公司的性能工程师Sergey Kuksenko有一篇很好的性能比较的文档: JDK 8: Lambda Performance
study, 详细而全面的比较了lambda表达式和匿名函数之间的性能差别。这里是视频。 16页讲到最差(capture)也和inner
class一样, non-capture好的情况是inner class的5倍。

lambda开发组也有一篇ppt, 其中也讲到了lambda的性能(包括capture和非capture的情况)。看起来lambda最差的情况性能内部类一样, 好的情况会更好。

Java 8 Lambdas - they are fast, very fast也有篇文章 (需要翻墙),表明lambda表达式也一样快。

Lambda的阴暗面

前面都是讲Lambda如何改变Java程序员的思维习惯,但Lambda确实也带来了困惑

JVM可以执行任何语言编写的代码,只要它们能编译成字节码,字节码自身是充分OO的,被设计成接近于Java语言,这意味着Java被编译成的字节码非常容易被重新组装。

但是如果不是Java语言,差距将越来越大,Scala源码和被编译成的字节码之间巨大差距是一个证明,编译器加入了大量合成类 方法和变量,以便让JVM按照语言自身特定语法和流程控制执行。

我们首先看看Java 6/7中的一个传统方法案例:


  1. // simple check against empty strings 
  2. public static int check(String s) { 
  3. if (s.equals("")) { 
  4. throw new IllegalArgumentException(); 
  5. return s.length(); 
  6.  
  7. //map names to lengths 
  8.  
  9. List lengths = new ArrayList(); 
  10.  
  11. for (String name : Arrays.asList(args)) { 
  12. lengths.add(check(name)); 
  13.  
  14. 如果一个空的字符串传入,这段代码将抛出错误,堆栈跟踪如下: 
  15.  
  16. at LmbdaMain.check(LmbdaMain.java:19) 
  17. at LmbdaMain.main(LmbdaMain.java:34) 

再看看Lambda的例子


  1. Stream lengths = names.stream().map(name -> check(name)); 
  2.  
  3. at LmbdaMain.check(LmbdaMain.java:19) 
  4. at LmbdaMain.lambda$0(LmbdaMain.java:37) 
  5. at LmbdaMain$$Lambda$1/821270929.apply(Unknown Source) 
  6. at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 
  7. at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) 
  8. at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) 
  9. at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) 
  10. at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) 
  11. at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) 
  12. at java.util.stream.LongPipeline.reduce(LongPipeline.java:438) 
  13. at java.util.stream.LongPipeline.sum(LongPipeline.java:396) 
  14. at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526) 
  15. at LmbdaMain.main(LmbdaMain.java:39) 

这非常类似Scala,出错栈信息太长,我们为代码的精简付出力代价,更精确的代码意味着更复杂的调试。

但这并不影响我们喜欢Lambda!

总结

在Java世界里面,面向对象还是主流思想,对于习惯了面向对象编程的开发者来说,抽象的概念并不陌生。面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。现实世界中,数据和行为并存,程序也是如此,因此这两种编程方式我们都得学。

这种新的抽象方式还有其他好处。很多人不总是在编写性能优先的代码,对于这些人来说,函数式编程带来的好处尤为明显。程序员能编写出更容易阅读的代码——这种代码更多地表达了业务逻辑,而不是从机制上如何实现。易读的代码也易于维护、更可靠、更不容易出错。

在写回调函数和事件处理器时,程序员不必再纠缠于匿名内部类的冗繁和可读性,函数式编程让事件处理系统变得更加简单。能将函数方便地传递也让编写惰性代码变得容易,只有在真正需要的时候,才初始化变量的值。

总而言之,Java更趋于完美了。

来源:51CTO

时间: 2024-11-05 14:42:23

Java 8里面lambda的最佳实践的相关文章

介绍在Java程序中记录日志的最佳实践

本文介绍了在Java程序中记录日志的最佳实践,同时也介绍了如何使用开源软件对日志进行聚合和分析.对于现在的应用程序来说,日志的重要性是不言而喻的.很难想象没有任何日志记录功能的应用程序运行在生产环境中.日志所能提供的功能是多种多样的,包括记录程序运行时产生的错误信息.状态信息.调试信息和执行http://www.aliyun.com/zixun/aggregation/18195.html">时间信息等.在生产环境中,日志是 查找问题来源的重要依据.应用程序运行时的产生的各种信息,都应该通

Java程序优化的一些最佳实践

摘要:本文介绍了Java代码优化的过程,总结了优化Java程序的一些最佳实践,分析了进行优化的方法并解释了性能提升的原因.多角度分析导致性能低的原因并逐个进行优化使得程序性能得到极大提升,代码可读性.可扩展性更强. 作者通过经历的一个项目实例,介绍Java代码优化的过程,总结了优化Java程序的一些最佳实践,分析了进行优化的方法,并解释了性能提升的原因.作者从多个角度分析导致性能低的原因,并逐个进行优化,最终使得程序的性能得到极大提升,增强了代码的可读性.可扩展性. 一.衡量程序的标准衡量一个程

Java开发中异常处理的最佳实践

异常处理是Java 开发中的一个重要部分.它是关乎每个应用的一个非功能性需求,是为了处理任何错误状况,比如资源不可访问,非法输入,空输入等等.Java提供了几个异常处理特性,以try,catch 和 finally 关键字的形式内建于语言自身之中.Java 编程语言也允许你创建新的异常,并通过使用 throw 和 throws关键字抛出它们.事实上,在Java编程中,Java的异常处理不单单是知道语法这么简单,它必须遵循标准的JDK库,和几个处理错误和异常的开源代码.这里我们将讨论一些关于异常处

将Flex集成到Java EE应用程序的最佳实践

简介:传统的 Java EE 应用程序通常使用某种 MVC 框架(例如,Struts)作为前端用户界面,随着 Flex 的兴起,基于 RIA 的客户端能够给用户带来更酷的界面,更短的响应时间,以及更接近于桌面应 用程序的体验.本文将讲述如何将 Flex 集成至一个现有的 Java EE 应用程序中,以及如何应用最佳实 践高效率地并行开发 Java EE 和 Flex. 开发环境 本文的开发环境为 Windows 7 Ultimate,Eclipse 3.4,Flex Builder 3.Java

Java 的最佳实践

Java 是在世界各地最流行的编程语言之一, 但是看起来没人喜欢使用它.而 Java 事实上还算是一门不错的语言,随着 Java 8 最近的问世,我决定编制一个库,实践和工具的清单,汇集 Java 的一些最佳实践. 本文被放到了 Github 上.你可以随意地提交贡献,并加入自己的有关 Java 方面的建议和最佳实践. 风格 Javadoc 构建器模式 结构 依赖注入 避免空值 默认不可变更 避免大量的工具类 格式化 流 发布 依赖收敛 框架 Maven 持续集成 Maven 资源库 配置管理

避免Java应用中NullPointerException的技巧和最佳实践

Java应用中抛出的空指针异常是解决空指针的最好方式,也是写出能顺利工作的健壮程序的关键.俗话说"预防胜于治疗",对于这么令人讨厌的空指针异常,这句话也是成立的.值得庆幸的是运用一些防御性的编码技巧,跟踪应用中多个部分之间的联系,你可以将Java中的空指针异常控制在一个很好的水平上.顺便说一句,这是Javarevisited上的第二个空指针异常的帖子.在上个帖子中我们讨论了Java中导致空指针异常的常见原因,而在本教程中我们将会学习一些Java的编程技巧和最佳实践.这些技巧可以帮助你避

Java中关于异常处理的10个最佳实践

Java 编程中异常处理的最佳实践 这里是我收集的10个Java编程中进行异常处理的10最佳实践.在Java编程中对于检查异常有褒有贬,强制处理异常是一门语言的功能.在本文中,我们将尽量减少使用检查型异常,同时学会在Java编程中使用检查型VS非检查型异常. 1.为可恢复的错误使用检查型异常,为编程错误使用非检查型错误 选择检查型还是非检查型异常,对于Java编程人员来说,总是让人感到困惑.检查型异常保证你对错误条件提供异常处理代码,这是一种从语言到强制你编写健壮的代码的一种方式,但同时会引入大

Java字符串拼接效率分析及最佳实践

本文来源于问题 Java字符串连接最佳实践? java连接字符串有多种方式,比如+操作符,StringBuilder.append方法,这些方法各有什么优劣(可以适当说明各种方式的实现细节)? 按照高效的原则,那么java中字符串连接的最佳实践是什么? 有关字符串处理,都有哪些其他的最佳实践? 废话不多说,直接开始, 环境如下: JDK版本: 1.8.0_65 CPU: i7 4790 内存: 16G 直接使用+拼接 看下面的代码: @Test      public void test() {

JSP最佳实践: 使用JSP include机制改进外观

简介:本文是新的 JSP 最佳实践系列文章的第一部分,它介绍了 JavaServer Pages include 机制. 请跟随 Java 编程专家 Brett McLaughlin 学习如何使用 include 将静态的头文件和脚注文件加入您的 网站或 Web 应用程序页面中. 欢迎阅读 Java 专区中最新的 最佳实践系列文章.如果您看过先前的系列文章,那么您会知道最佳实 践旨在让您快速了解各种 Java 技术的有用方面.这一系列的最佳实践专门讨论 JavaServer Pages(JSP