Lambda表達式


Java 8歷時2年8個月,這次升級是繼Java 5之后對Java一次脫胎換骨的變化。在Java 8的新特性中很多都是圍繞Lambda表達式而提供的,Lambda表達式也將使熱衷於OOP(Object-Oriented Programming)的Java程序員體會到FP(Functional Programming)的強大。Java 8的Lambda表達多少借鑒了Scala的Lambda(Scala使用「=>」Java8使用「->」),和純正FP的Haskell還是有很大差別。 

(1)歷史來由 

①關於Java7/Java8 
Java 8的很多新特性按照預定都應該包含在Java 7中,但是由於對Project Lambda和Project Jigsaw的爭論一直沒有達成共識,Java 7一直無法發布。2010年9月Java平台首席架構師Mark Reinhold在他的Blog上提出2個方案: 

  • A:所有功能都包含到Java 7中,發布推遲到2012年中旬
  • B:不包含Project Lambda和Project Jigsaw等幾個項目Java 7在2011年中旬發布,這幾個項目推遲到2012年中旬發布的Java 8中

博客的評論基本上都贊成方案B。2010年的JavaOne上Mark Reinhold正式決定采用方案B。之后2011年7月Java 7正式發布。 

2012年7月 Mark Reinhold再次發文稱Project Jigsaw將不會包含在Java 8中而得推遲到Java 9中,由此一來Java 8的主要特性就是Project Lambda。 

2012年下旬->2013年下旬->2014年3月 Java 8的發布也是不斷跳票。Project Coin、Collection Literals、Swing Date Picker等也被推遲到Java 9,而把Date & Time API、Nashorn等納入Java 8中。 

2014年3月18日 Java 8帶着“即使有Bug也要發布”的節奏,歷時2年8個月終於發布了。JavaTM SE 8 Release Contents 

②關於Project Lambda 
2005年,Java 6發布前很多人提議在Java中引入閉包Closure,先是Gilad Bracha、Neal Gafter、James Gosling、Peter van der Ahé做了一個Java閉包的提案,叫BGGA(以他們名字的首字母命名)、后來改名Closures for the Java Programing Language。關於這個提案在Java社區掀起了很到爭論,Neal Gafter和Joshua Bloch爭論最為有名。 

除BGGA外,Java閉包的實現也有很多其他的方案,比如:Joshua Bloch、Doug Lea、Bob Lee提出了CICE(Concise Instance Creation Expresions)。還有一個提案是FCM(First Class Methods)。CICE並不是要在Java中引入閉包而是對匿名類做一個簡化寫法的提案(現在最終的Project Lambda也是很接近CICE)。 

2008年在java.net做了一個在線調查,大概2000人參與回答,大部分人投給了BGGA或者干脆不引入閉包。 

2008年11月,Mark Reinhold在 Devoxx 發布Java 7中不會引入閉包,從而才終止了這場爭論。但是1年后的2009年11月Mark Reinhold同樣是在 Devoxx 發布Java 7中要引入簡單的閉包。基於此,2009年12月作為OpenJDK的子項目Project Lambda開始啟動。 

 

為什么2008年終止在Java 7中引入閉包會在2009年再次提出? 
答案是:CPU多核化的趨勢。無論是PC還是SmartPhone,CPU的多核化在急速發展,所以軟件的並行處理也要跟得上。 

Java中雖然可以通過Thread來並行處理,但是Java 5的Concurrency Utilities的處理粒度更大,而Java 7的Fork/Join Framework並行粒度更細。Java的for循環采用外部迭代(程序員自己寫代碼),需要轉成內部迭代(由Java類庫迭代)的並行化,而很多語言都已經支持內部迭代。雖然Java可以借助匿名類來實現,但是寫法很繁瑣,所以需要簡化匿名類的寫法。 

2009年Project Lambda成立后,Mark Reinhold提出了一個原案Straw-Man Proposal。在這個稻草人提議中Mark Reinhold並沒有使用以前爭論不休的閉包,而是采用了Lambda表達式。基於此草案不斷演化才得到了現在Java8的Lambda表達式。JSR 335: Lambda Expressions for the Java™ Programming Language: 

接口的變化: 
開始決定使用SAM(Single Abstract Method)來作為接口,SAM指只有一個方法的抽象類或接口。但函數型的引入又激起了爭論,后來只限於接口,所以很少人再說SAM,而是引入了函數式接口的概念。 

寫法的變化: 
#(參數) { 函數體 } -> (參數) -> { 函數體 } 
Collections.xxxx() -> Stream API 

實現的變化 
匿名類 ->Java 7 引入的 InvokeDynamic命令動態生成Class 

Project Lambda是一個漫長的過程,能夠最終成行已經不易,看看這些人的名字,他們都是Java界的大神,尤其Doug Lea為Java並發做出了不可磨滅的貢獻。 

(2)Lambda表達式 

λ:希臘字母表中排序第十一位的字母,英語名稱為Lambda。最早出現是用於計算的λ演算(lambda calculus),后來被函數式編程語言廣泛采用。 

Lambda表達式可以理解成為是一個能夠作為參數傳遞的匿名函數Object,他沒有名字,但有參數列表、有函數體、有返回類型、可以拋出異常。它的類型,叫做“目標類型(target type)”Java8中就是“函數接口(functional interface)”。 

語法: 

引用
(parameters) -> expression 
(parameters) -> statement 
(parameters) -> { statements; }



舉例: 

引用
() -> Math.PI * 2.0 
(int i) -> i * 2 
(String s) -> s.length() 
(int i0, int i1) -> i0 + i1 
(int x, int y) -> { return x + y; }



①省略類型 

Java代碼   收藏代碼
  1. (int x, int y) -> { return x + y; };  
  2. (x, y) -> { return x + y; };  


***不能只省略一部分類型,比如:(int x, y) -> { return x + y; }; // NG 

②1個參數可以省略括號 

Java代碼   收藏代碼
  1. (String text) -> { System.out.println(text); };  
  2. (text) -> { System.out.println(text); };  
  3. text -> { System.out.println(text); };  


***但是不能帶類型,比如:String text -> { System.out.println(text); }; // NG 

③函數體多行時需要大括號 

Java代碼   收藏代碼
  1. (int i) -> {  
  2.     int prod = 1;  
  3.     for(int n = 0; n < 5; n++) prod *= i;  
  4.     return prod;  
  5. };  



④函數體只有一行的話可以省略大括號 

Java代碼   收藏代碼
  1. text -> System.out.println(text);  


***assert語句不能省略帶括號,比如: s -> { assert !s.isEmpty(); }; 
***return語句不能省略帶括號,比如:(x, y) -> return x -y; // NG 

⑤只有一行代碼而且有返回值的可以省略return,會返回該行代碼計算結果 

Java代碼   收藏代碼
  1. (x, y) -> { return x -y; };  
  2. (x, y) -> x -y;  


***return要和大括號一起省略,比如:(x, y) -> { x - y; }; // NG 

⑥沒有參數沒有返回值的空函數 

Java代碼   收藏代碼
  1. () -> {};   



⑦Scala等使用下划線做占位符,Java8中不可以 

Java代碼   收藏代碼
  1. (_) -> System.out.println(_); // NG  



⑧用於Lambda的變量不可變 

Java代碼   收藏代碼
  1. int portNumber = 1337;  
  2. Runnable r = () -> System.out.println(portNumber); // OK  
  3.   
  4. // 編譯錯誤  
  5. // Local variable portNumber defined in an enclosing scope must be final or effectively final  
  6. int portNumber = 1337;  
  7. Runnable r = () -> System.out.println(portNumber); // NG  
  8. portNumber = 1338;  
  9.   
  10. // 通過數組實現  
  11. final int[] wrappedNumber = new int[] { 1337 };  
  12. Runnable r = () -> System.out.println(wrappedNumber[0]); // OK  
  13. wrappedNumber[0] = 1338;  



⑨其他 

  • 一個λ表達式可以有多個目標類型(函數接口),只要函數匹配成功即可。但需注意一個λ表達式必須至少有一個目標類型。
  • 一個λ表達式可以被當做Object使用,需要賦值給一個函數接口,然后再賦值給一個Object。


(3)Lambda和匿名類區別 
this:匿名類this取得是自己,Lambda取得是所在的外部類。 

Java代碼   收藏代碼
  1. public class LambdaTest {  
  2.     public LambdaTest() {  
  3.         Function func1 = new Function() {  
  4.             @Override  
  5.             public void func() {  
  6.                 System.out.println("Anon Class: " + this.getClass());  
  7.             }  
  8.         };  
  9.         Function func2 = () -> System.out.println("Lambda Exp.: " + this.getClass());  
  10.     }  
  11. }  


****但是很少需要在lambda中使用this 

(4)用於何處 
Lambda可以傳遞給任何希望是函數式接口的地方。 

(5)運行機制 
Java 7引入了invokedynamic指令,它是一個JVM指令,允許動態語言在run-time時動態綁定。Java 8的Lambda表達式並不是匿名類的語法糖,它不會在編譯的時候生成類似於匿名類的xxx$1.class,而是在運行的時候使用invokeDynamic指令。對於一條Lambda表達式在class里邊會包含一個invokedynamic命令和一個靜態方法。運行時會使用LambdaMetafactory#metafactory做成一個Lambda$1的內部類再調用該函數式接口的實例。在運行時生成class,就是避免class太多影響加載速度,像Stream那樣的到處是Lambda。 



比如遍歷List: 

Java代碼   收藏代碼
  1. Arrays.asList("a", "b", "c").forEach(x -> System.out.println(x));  



編譯后的Class內容: 
 

 

也可以接住異常堆棧信息看看Lambda是怎么執行的: 

Java代碼   收藏代碼
  1. String[] datas = new String[]{""};  
  2. Arrays.asList(datas).stream().forEach(name -> check(name));  
  3.   
  4. public static int check(String s) {  
  5.     if (s.equals("")) {  
  6.         throw new IllegalArgumentException();  
  7.     }  
  8.     return s.length();  
  9. }  



引用
Exception in thread "main" java.lang.IllegalArgumentException 
    at com.rensanning.java8.lambda.Test.check(Test.java:14) 
    at com.rensanning.java8.lambda.Test.lambda$0(Test.java:9) 
    at com.rensanning.java8.lambda.Test$$Lambda$1/518248.accept(Unknown Source) 
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) 
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) 
    at com.rensanning.java8.lambda.Test.main(Test.java:9)



參考: 
http://www.oracle.com/events/us/en/java8/index.html 
http://www.lambdafaq.org/ 
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html 
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html 
http://www.dreamsyssoft.com/java-8-lambda-tutorial/index.php 
http://www.slideshare.net/bitter_fox/java8-launch 
http://www.slideshare.net/miyakawataku/lambda-meets-invokedynamic 
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html 
http://www.slideshare.net/mariofusco/fp-in-java-project-lambda-and-beyond 
http://www.journaldev.com/2389/java-8-features-for-developers-lambdas-functional-interface-stream-and-time-api


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM