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) -> statement
(parameters) -> { statements; }
舉例:
(int i) -> i * 2
(String s) -> s.length()
(int i0, int i1) -> i0 + i1
(int x, int y) -> { return x + y; }
①省略類型
- (int x, int y) -> { return x + y; };
- (x, y) -> { return x + y; };
***不能只省略一部分類型,比如:(int x, y) -> { return x + y; }; // NG
②1個參數可以省略括號
- (String text) -> { System.out.println(text); };
- (text) -> { System.out.println(text); };
- text -> { System.out.println(text); };
***但是不能帶類型,比如:String text -> { System.out.println(text); }; // NG
③函數體多行時需要大括號
- (int i) -> {
- int prod = 1;
- for(int n = 0; n < 5; n++) prod *= i;
- return prod;
- };
④函數體只有一行的話可以省略大括號
- text -> System.out.println(text);
***assert語句不能省略帶括號,比如: s -> { assert !s.isEmpty(); };
***return語句不能省略帶括號,比如:(x, y) -> return x -y; // NG
⑤只有一行代碼而且有返回值的可以省略return,會返回該行代碼計算結果
- (x, y) -> { return x -y; };
- (x, y) -> x -y;
***return要和大括號一起省略,比如:(x, y) -> { x - y; }; // NG
⑥沒有參數沒有返回值的空函數
- () -> {};
⑦Scala等使用下划線做占位符,Java8中不可以
- (_) -> System.out.println(_); // NG
⑧用於Lambda的變量不可變
- int portNumber = 1337;
- Runnable r = () -> System.out.println(portNumber); // OK
- // 編譯錯誤
- // Local variable portNumber defined in an enclosing scope must be final or effectively final
- int portNumber = 1337;
- Runnable r = () -> System.out.println(portNumber); // NG
- portNumber = 1338;
- // 通過數組實現
- final int[] wrappedNumber = new int[] { 1337 };
- Runnable r = () -> System.out.println(wrappedNumber[0]); // OK
- wrappedNumber[0] = 1338;
⑨其他
- 一個λ表達式可以有多個目標類型(函數接口),只要函數匹配成功即可。但需注意一個λ表達式必須至少有一個目標類型。
- 一個λ表達式可以被當做Object使用,需要賦值給一個函數接口,然后再賦值給一個Object。
(3)Lambda和匿名類區別
this:匿名類this取得是自己,Lambda取得是所在的外部類。
- public class LambdaTest {
- public LambdaTest() {
- Function func1 = new Function() {
- @Override
- public void func() {
- System.out.println("Anon Class: " + this.getClass());
- }
- };
- Function func2 = () -> System.out.println("Lambda Exp.: " + this.getClass());
- }
- }
****但是很少需要在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:
- Arrays.asList("a", "b", "c").forEach(x -> System.out.println(x));
編譯后的Class內容:
也可以接住異常堆棧信息看看Lambda是怎么執行的:
- String[] datas = new String[]{""};
- Arrays.asList(datas).stream().forEach(name -> check(name));
- public static int check(String s) {
- if (s.equals("")) {
- throw new IllegalArgumentException();
- }
- return s.length();
- }
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