Java 8中的Lambda表示式最佳實踐
Java 8已經推出一段時間了,越來越多開發人員選擇升級JDK,這條熱門動彈裡面看出,JDK7最多,其次是6和8,這是好事!
在Java 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表示式替代醜陋的匿名類。
//Before Java 8: new Thread(new Runnable() { @Override public void run() { System.out.println("Before Java8 "); } }).start(); //Java 8 way: new Thread(() -> System.out.println("In Java8!")); // Before Java 8: JButton show = new JButton("Show"); show.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("without lambda expression is boring"); } }); // Java 8 way: show.addActionListener((e) -> { System.out.println("Action !! Lambda expressions Rocks"); });
2.使用內迴圈替代外迴圈
外迴圈:描述怎麼幹,程式碼裡巢狀2個以上的for迴圈的都比較難讀懂;只能順序處理List中的元素;
內迴圈:描述要幹什麼,而不是怎麼幹;不一定需要順序處理List中的元素
//Prior Java 8 : List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API"); for (String feature : features) { System.out.println(feature); } //In Java 8: List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API"); features.forEach(n -> System.out.println(n)); // Even better use Method reference feature of Java 8 // method reference is denoted by :: (double colon) operator // looks similar to score resolution operator of C++ features.forEach(System.out::println); Output: Lambdas Default Method Stream API Date and Time API
3.支援函式程式設計
為了支援函式程式設計,Java 8加入了一個新的包java.util.function,其中有一個介面java.util.function.Predicate是支援Lambda函式程式設計:
public static void main(args[]){ List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp"); System.out.println("Languages which starts with J :"); filter(languages, (str)->str.startsWith("J")); System.out.println("Languages which ends with a "); filter(languages, (str)->str.endsWith("a")); System.out.println("Print all languages :"); filter(languages, (str)->true); System.out.println("Print no language : "); filter(languages, (str)->false); System.out.println("Print language whose length greater than 4:"); filter(languages, (str)->str.length() > 4); } public static void filter(List names, Predicate condition) { names.stream().filter((name) -> (condition.test(name))) .forEach((name) -> {System.out.println(name + " "); }); } Output: Languages which starts with J : Java Languages which ends with a Java Scala Print all languages : Java Scala C++ Haskell Lisp Print no language : Print language whose length greater than 4: Scala Haskell
4.處理資料?用管道的方式更加簡潔
Java 8裡面新增的Stream API ,讓集合中的資料處理起來更加方便,效能更高,可讀性更好
假設一個業務場景:對於20元以上的商品,進行9折處理,最後得到這些商品的折後價格。
final BigDecimal totalOfDiscountedPrices = 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("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中的一個傳統方法案例:
// simple check against empty strings public static int check(String s) { if (s.equals("")) { throw new IllegalArgumentException(); } return s.length(); } //map names to lengths List lengths = new ArrayList(); for (String name : Arrays.asList(args)) { lengths.add(check(name)); }
如果一個空的字串傳入,這段程式碼將丟擲錯誤,堆疊跟蹤如下:
at LmbdaMain.check(LmbdaMain.java:19) at LmbdaMain.main(LmbdaMain.java:34)
再看看Lambda的例子
Stream lengths = names.stream().map(name -> check(name)); at LmbdaMain.check(LmbdaMain.java:19) at LmbdaMain.lambda$0(LmbdaMain.java:37) at LmbdaMain$$Lambda$1/821270929.apply(Unknown Source) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.LongPipeline.reduce(LongPipeline.java:438) at java.util.stream.LongPipeline.sum(LongPipeline.java:396) at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526) at LmbdaMain.main(LmbdaMain.java:39)
這非常類似Scala,出錯棧資訊太長,我們為程式碼的精簡付出力代價,更精確的程式碼意味著更復雜的除錯。
但這並不影響我們喜歡Lambda!
總結
在Java世界裡面,物件導向還是主流思想,對於習慣了物件導向程式設計的開發者來說,抽象的概念並不陌生。物件導向程式設計是對資料進行抽象,而函數語言程式設計是對行為進行抽象。現實世界中,資料和行為並存,程式也是如此,因此這兩種程式設計方式我們都得學。
這種新的抽象方式還有其他好處。很多人不總是在編寫效能優先的程式碼,對於這些人來說,函數語言程式設計帶來的好處尤為明顯。程式設計師能編寫出更容易閱讀的程式碼——這種程式碼更多地表達了業務邏輯,而不是從機制上如何實現。易讀的程式碼也易於維護、更可靠、更不容易出錯。
在寫回撥函式和事件處理器時,程式設計師不必再糾纏於匿名內部類的冗繁和可讀性,函數語言程式設計讓事件處理系統變得更加簡單。能將函式方便地傳遞也讓編寫惰性程式碼變得容易,只有在真正需要的時候,才初始化變數的值。
總而言之,Java更趨於完美了。
相關文章
- Java的Lambda表示式Java
- Java | Lambda表示式Java
- Lambda表示式(Java)Java
- Java Lambda表示式Java
- Java 中的 Lambda 表示式Java
- 深入理解 Java 多執行緒、Lambda 表示式及執行緒安全最佳實踐Java執行緒
- Java 8 Lambda 表示式Java
- java 8 lambda表示式Java
- Java之lambda表示式Java
- Java8的Lambda表示式Java
- Java中Lambda表示式的使用Java
- 利用 Lambda 表示式實現 Java 中的惰性求值Java
- Java筆記:Lambda表示式Java筆記
- Java 基礎 —— Lambda 表示式Java
- Java lambda表示式基本使用Java
- Java8-Lambda表示式Java
- Java中Lambda表示式的應用Java
- Java8中的Lambda表示式Java
- java8特性-lambda表示式Java
- Java中lambda表示式詳解Java
- Java中Lambda表示式的進化之路Java
- lambda 表示式
- lambda表示式
- 《Java 8 in Action》Chapter 3:Lambda表示式JavaAPT
- Java 8新特性(一):Lambda表示式Java
- Java 8 lambda 表示式10個示例Java
- Java入門:Lambda常用表示式解析Java
- cpp的lambda表示式
- <七>lambda表示式實現原理
- Java 的各種內部類、Lambda表示式Java
- C# Lambda表示式詳解,及Lambda表示式樹的建立C#
- 【Kotlin】Lambda表示式Kotlin
- CPP lambda表示式
- 八,Lambda表示式
- Python Lambda 表示式Python
- Python - lambda 表示式Python
- kotlin lambda表示式Kotlin
- java8學習:lambda表示式(2)Java
- java8學習:lambda表示式(1)Java