当前位置: 首页 > 科技观察

Java8中lambda的最佳实践

时间:2023-03-12 03:18:35 科技观察

Java8已经推出一段时间了,越来越多的开发者选择升级JDK。从这个热门消息可以看出JDK7最多,其次是6和8,这是好事!8、Lambda是最热门的话题,不仅仅是因为语法的改变,更重要的是它带来了函数式编程的思想。我觉得优秀的程序员很有必要去学习函数式编程的思想,开阔自己的思路。.所以这篇文章讲了Lambda的应用场景和性能,也提到了不好的一面。为什么Java需要Lambda1996年1月,Java1.0发布,自此计算机编程领域发生了翻天覆地的变化。业务发展需要更复杂的应用程序,大多数程序运行在配备多核CPU的更强大的机器上。具有高效运行时编译器的Java虚拟机(JVM)的出现,让程序员可以更专注于编写干净且易于维护的代码,而不是考虑如何利用每个CPU时钟和每个字节的内存充分利用一切。多核CPU的出现,已经成为不可忽视却又无人愿意正视的“房间里的大象”。算法中引入锁不仅容易出错,而且耗时。人们开发了java.util.concurrent包和许多第三方库,试图抽象并发性以帮助程序员编写在多核CPU上运行良好的程序。不幸的是,到目前为止我们走的还不够远。当那些库的开发人员使用Java时,他们发现抽象级别不够。处理大数据就是一个很好的例子。面对大数据,Java仍然缺乏高效的并行运算。Java8允许开发者编写复杂的集合处理算法,只需要修改一个方法就可以让代码在多核CPU上高效运行。为了编写并行处理这些大数据的类库,需要在语言层面修改现有的Java:添加lambda表达式。当然,这是有代价的,程序员必须学习如何编写和阅读包含lambda表达式的代码,但这并不是一件坏事。学习一点新语法和一些新习惯比手工编写一大块复杂的、线程安全的代码要容易得多。在开发企业级应用时,好的类库和框架可以大大减少开发时间和成本,也为开发易用高效的类库扫清了障碍。如果您还没有接触过Lambda的语法,可以在这里阅读。对于Lambda的应用场景,学习函数式编程的概念是很有必要的,比如对函数式编程的初步探索,但下面我会着重介绍函数式编程的实用性,包括那些大多数人都能理解和使用的技术程序员,我们关心的是如何写出好的代码,而不是符合函数式编程风格的代码。1.使用()->{}代替匿名类现在Runnable线程、Swing、JavaFX事件监听器代码等,在java8中你可以使用Lambda表达式代替丑陋的匿名类。//BeforeJava8:newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println("BeforeJava8");}}).start();//Java8way:newThread(()->System.out.println("InJava8!"));//BeforeJava8:JButtonshow=newJButton("Show");show.addActionListener(newActionListener(){@OverridepublicvoidactionPerformed(ActionEvente){System.out.println("withoutlambdaexpressionisboring");}});//Java8way:show.addActionListener((e)->{System.out.println("Action!!LambdaexpressionsRocks");});2.用内循环代替外循环Outerloop:描述怎么做,代码中嵌套了2个以上的for循环,阅读困难;只能顺序处理List中的元素;内循环:描述做什么,而不是如何做;不需要按顺序处理List中的元素//PriorJava8:Listfeatures=Arrays。asList("Lambdas","DefaultMethod","StreamAPI","DateandTimeAPI");for(Stringfeature:features){System.out.println(feature);}//InJava8:Listfeatures=Arrays.asList("Lambdas","DefaultMethod","StreamAPI","DateandTimeAPI");features.forEach(n->System.out.println(n));//甚至更好地使用Java8的方法引用特性//方法引用由::(双冒号)运算符表示//看起来类似于C++features.forEach(System.out::println)的分数解析运算符;输出:LambdasDefaultMethodStreamAPIDateandTimeAPI3。支持函数式编程为了支持函数式编程,Java8新增了一个包java.util.function,它有一个接口java.util.function.Predicate,支持Lambda函数式编程:publicstaticvoidmain(args[]){Listlanguages=Arrays.asList("Java","Scala","C++","Haskell","Lisp");System.out.println("LanguageswhichstartswithJ:");过滤器(语言,(str)->str.startsWith("J"));System.out.println("Languageswhichendswitha");filter(languages,(str)->str.endsWith("a"));System.out.println("Printalllanguages:");filter(语言,(str)->true);System.out.println("Printnolanguage:");filter(languages,(str)->false);System.out.println("Printlanguagewhoselengthgreaterthan4:");filter(languages,(str)->str.length()>4);}publicstaticvoidfilter(Listnames,Predicatecondition){names.stream().filter((name)->(condition.test(name))).forEach((name)->{System.out.println(name+"");});}输出:以J开头的语言:以JavaScala结尾的JavaLanguagesPrintall语言:JavaScalaC++HaskellLispPrintnolanguage:长度大于4的打印语言:ScalaHaskell4。处理数据?流水线方法更简洁。Java8中新的StreamAPI可以更方便地处理集合中的数据,具有更高的性能和更好的可读性。假设一个业务场景:商品满20元立减10%,最终得到这些商品的折扣价finalBigDecimaltotalOfDiscountedPrices=prices.stream().filter(price->price.compareTo(BigDecimal.valueOf(20))>0).map(price->price.multiply(BigDecimal.valueOf(0.9))).reduce(BigDecimal.ZERO,BigDecimal::add);System.out.println("Totalofdiscountedprices:"+totalOfDiscountedPrices);想象一下:用面向对象的方式处理这些数据需要多少行代码?多少圈?需要声明多少个中间变量?关于StreamAPI的更多信息,可以查看我之前的文章。#p#Lambda性能Oracle公司的性能工程师SergeyKuksenko有一个非常好的性能比较文档:JDK8:LambdaPerformancestudy,其中对lambda表达式和匿名函数之间的性能差异进行了详细而全面的比较。这是视频。16页说最差的(捕获)和内部类一样,好的非捕获是内部类的5倍。lambda开发组还有一个ppt,里面也讲了lambda的性能(包括捕获和非捕获情况)。看起来lambda在最坏的情况下表现得和内部类一样好,在更好的情况下表现更好。Java8Lambdas-它们很快,非常快也有一篇文章(需要绕过墙),表明lambda表达式同样快。Lambda的阴暗面前面几章讲了Lambda是如何改变Java程序员的思维习惯的,但是Lambda确实也带来了困惑。JVM可以执行任何语言编写的代码,只要能编译成字节码,字节码本身是完全面向对象的,旨在接近Java语言,这意味着编译成Java的字节码非常容易重新组装。但是如果不是Java语言,差距会越来越大。Scala源代码和编译后的字节码之间的巨大差距就是一个证明。编译器添加了大量的合成类方法和变量,让JVM遵循语言自己特定的语法。和过程控制执行。先来看一个Java6/7中的传统方法案例://simplecheckagainstemptystringspublicstaticintcheck(Strings){if(s.equals("")){thrownewIllegalArgumentException();}returns.length();}//mapnamestolengthsListlengths=newArrayList();for(Stringname:Arrays.asList(args)){lengths.add(check(name));}如果传入空字符串,此代码将抛出错误并显示以下堆栈跟踪:?12atLmbdaMain。check(LmbdaMain.java:19)atLmbdaMain.main(LmbdaMain.java:34)查看Lambda示例Streamlengths=names.stream().map(name->check(name));atLmbdaMain.check(LmbdaMain.java:19)atLmbdaMain.lambda$0(LmbdaMain.java:37)atLmbdaMain$$Lambda$1/821270929.apply(UnknownSource)atjava.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)atjava.util.Spliterators$数组分割器.forEachRemaining(Spliterators.java:948)在java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)在java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)atjava.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)在java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)atjava.util.stream.LongPipeline.reduce(LongPipeline.java:438)atjava.util.stream.LongPipeline.sum(LongPipeline.java:396)atjava.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)atLmbdaMain.main(LmbdaMain.java:39)这个和Scala很像,错误栈信息太长,我们为简化代码付出了代价,更多准确的代码意味着更复杂的调试,但这并不能阻止我们喜欢Lambda!综上所述,在Java的世界里,面向对象依然是主流思想。对于习惯了面向对象编程的开发者来说,抽象这个概念并不陌生。面向对象编程是关于抽象数据,而函数式编程是关于抽象行为。在现实世界中,数据和行为是共存的,程序也是如此,所以我们必须同时学习这两种编程方法。这种新的抽象还有其他好处。对于许多并不总是编写性能优先代码的人来说,函数式编程的好处尤为明显。程序员可以编写更易于阅读的代码——表达业务逻辑的代码多于其如何机械地实现。可读代码也更易于维护、更可靠且不易出错。在编写回调函数和事件处理程序时,程序员不必再担心匿名内部类的冗长性和可读性。函数式编程使事件处理系统更容易。能够方便地传递函数也使得编写惰性代码变得容易,只在真正需要时才初始化变量值。总而言之,Java更完美。