本文首發於 blog.zhaochunqi.com 轉載請注明 blog.zhaochunqi.com
根據JSR 335, Java 終於在 Java 8 中引入了 Lambda 表達式。也稱之為閉包或者匿名函數。

JSR 335
所謂的 JSR (Java Specification Requests) 全稱叫做 Java 規范提案。簡單來說就是向 Java 社區提交新的 API 或 服務 請求的提案。這些提案將作為 Java 社區進行 Java 語言開發的需求,引導着開發的方向。
JSR 335 的提案內容摘要如下:
This JSR will extend the Java Programming Language Specification and the Java Virtual Machine Specification to support the following features:
- Lambda Expressions
- SAM Conversion
- Method References
- Virtual Extension Methods
也就是如下幾點:
- 支持 lambda 表達式。
- 支持 SAM conversion 用來向前兼容。
- 方法引用 Method References
- Virtual Extension Methods
在 Java 8 中,以上均已經實現,以上內容下文均有介紹。
為什么需要 Lambda 表達式?
Lambda 表達式,其實就是代碼塊。

原來怎么處理
在具體了解 lambda 之前,我們先往后退一步,看看之前我們是如何處理這些代碼塊的!
例子一
當決定在單獨的線程運行某程序時,你這樣做的
class Worker implements Runnable { public void run() { for (int i = 0; i < 1000; i++) doWork(); } ... }
這樣執行:
Worker w = new Worker(); new Thread(w).start();
Worker 中包含了你要執行的代碼塊。
例子二
如果你想實現根據字符串長度大小來排序,而不是默認的字母順序,你可以自己來實現一個 Comparator 用來 Sort。
class LengthComparator implements Comparator<String> { public int compare(String first, String second) { return Integer.compare(first.length(), second.length()); } } Arrays.sort(strings, new LengthComparator());
例子三
另外一個例子,我選的是 Android 中的點擊事件,同樣是 Java:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, "Hello World!", Toast.LENGTH_SHORT).show(); } });
上面代碼有什么問題呢?

它們都太復雜了啊!
上述例子都是在某個類中實現某個接口,然后傳遞到另外一個方法中作為參數,然后用來執行。
但是本質上,他們要傳遞的就是接口中那一個方法的實現而已啊!有必要先創建類,再實例化,再傳遞給調用的位置嗎?
因為 Java 是純面向對象的語言,像其他語言那樣隨隨便便傳個方法過來,那可不行,必須要這樣。
在其他語言中你可能可以,但是,在Java 中,不可以。

Java 設計人員為了 Java 的簡潔跟連貫性,一直拒絕為Java添加這種功能。(這也是我喜歡Java而不喜歡Python的原因啊!!!)
經過多年的努力,開發人員終於找到了符合 Java 編程習慣的 Lambda 表達式!
Lambda 表達式語法(Syntax)
考慮下前面的例子:
Integer.compare(first.length(), second.length())
first和second都是 String 類型,Java 是強類型的語言,必須指定類型:
(String first, String second)
-> Integer.compare(first.length(), second.length())

看到沒有!第一個 Lambda 表達式誕生了!!輸入、輸出簡潔明了!
為什么叫 Lambda 呢,這個很多年以前,有位邏輯學家想要標准化的表示一些可以被計算的數學方程(實際上存在,但是很難被表示出來),他就用 ℷ 來表示。
重新介紹一下 Java 中 Lambda 表達式的格式:
(參數) -> 表達式
多返回值
如果計算的結果並不由一個單一的表達式返回(換言之,返回值存在多種情況),使用“{}",然后明確指定返回值。
(String first, String second) -> {
if (first.length() < second.length()) return -1; else if (first.length() > second.length()) return 1; else return 0; }
無參數
如果沒有參數,則 "()"中就空着。
() -> { for (int i = 0; i < 1000; i++) doWork(); }
省略
如果參數的類型可以被推斷出,則可以直接省略
Comparator<String> comp
= (first, second) // Same as (String first, String second) -> Integer.compare(first.length(), second.length());
這里,first和second可以被推斷出是 String 類型,因為 是一個 String 類型的 Comparator。
如果單個參數可以被推斷出,你連括號都可以省略:
EventHandler<ActionEvent> listener = event ->
System.out.println("Thanks for clicking!"); // Instead of (event) -> or (ActionEvent event) ->
修飾符
你可以像對待其他方法一樣,annotation,或者 使用 final 修飾符
(final String name) -> ... (@NonNull String name) -> ...
永遠不要定義 result 的類型,lambda 表達式總是從上下文中推斷出來的:
(String first, String second) -> Integer.compare(first.length(), second.length())
注意
注意,在lambda 表達式中,某些分支存在返回值,某些不存在返回值這樣的情況是不允許的。
如 (int x) -> { if (x >= 0) return 1; }
這樣是非法的。
函數式接口(Functional Interfaces/SAM)
要介紹 Java 中 lambda 表達式的實現,需要知道什么是 函數式接口。
什么叫作函數式接口呢(SAM)?
函數式接口指的是只定義了唯一的抽象方法的接口(除了隱含的Object對象的公共方法), 因此最開始也就做SAM類型的接口(Single Abstract Method)。
Lambda 表達式向前兼容這些接口。
Comparable
舉個例子 Array.sort:
Arrays.sort(words,
(first, second) -> Integer.compare(first.length(), second.length()));
Array.sort() 方法收到一個實現了 Comparable<String> 接口的實例。
其實可以把 Lambda 表達式想象成一個方法,而非一個對象,一個可以傳入一個接口的方法。
OnClickListener
再舉個例子
button.setOnClickListener(event ->
System.out.println("Thanks for clicking!"));
你看,是不是更易讀了呢?
Lambda 表達式能夠向前兼容這些 interfaces, 太棒了! 那 Lambda 表達式還能干什么呢?
實際上,將函數式接口轉變成 lambda 表達式是你在 Java 中唯一能做的事情。

Why ?!!
在其他的語言中,你可以定義一些方便的方法類型,但在 Java 中,你甚至不能將一個Lambda表達式賦值給類型為 Object 的變量,因為 Object 變量不是一個 Functional Interface。
Java 的設計者們堅持使用熟悉的 interface 概念而不是為其引入新的 方法類型。
(這里我還要為設計者點贊!謹慎的設計,一方面降低了初學者的門檻,一方面方便了高級用戶的使用。對比 python2和 python3,升級的不兼容讓很多人一直停留在 python2)
Method References
能不能再簡潔一點?有的時候我們所要做的事情不過是調用其他類中方法來處理事件。
button.setOnClickListener(event -> System.out.println(event));
如果這樣呢?
button.setOnAction(System.out::println);
表達式 System.out::println
屬於一個方法引用(method reference), 相當於 lambda 表達式 x -> System.out.println(x)

再舉個例子,如果你想對字符串不管大小寫進行排序,就可以這樣寫!
Arrays.sort(strings, String::compareToIgnoreCase)
如上所見 ::
操作符將方法名與實例或者類分隔開。總體來說,又如下的規則:
- object::instanceMethod
- Class::staticMethod
- Class::instanceMethod
值得指出的是, this
和super
關鍵字可以在其中使用:
class Greeter { public void greet() { System.out.println("Hello, world!"); } }
class ConcurrentGreeter extends Greeter { public void greet() { Thread t = new Thread(super::greet); t.start(); } }
構造方法引用 Constructor References
跟上一個差不多,畢竟構造方法 也是方法啊!!不過方法名字為 new 。
但是!這個構造方法引用有一個牛逼的地方!
你知道 Array 是不能使用范型的對吧!(什么,你不知道?看看這里 http://stackoverflow.com/questions/2927391/whats-the-reason-i-cant-create-generic-array-types-in-java),你沒有辦法創建一個類型為 T 的 Array 。 new T[n] 將會被覆蓋為 new Object[n]。
假設我們想要一個包含 buttons 的 Array。Stream interface 可以返回一個 Object array。
Object[] buttons = stream.toArray();
不不不,我們可不想要 Object。Stream 庫使用 構造方法引用解決了這個問題:
Button[] buttons = stream.toArray(Button[]::new);

變量作用域
注意到我們在題目中寫着 閉包(closure),實際上,閉包的定義是: 引用了自由變量的函數。
在之前,如果需要在匿名類的內部引用外部變量,需要將外部變量定義為 final ,現在有了 lambda 表達式,你不必再這么做了。但同樣需要保證外部的自由變量不能在 lambda 表達式中被改變。

這是什么意思呢? 不需要定義為 final,也不能改?
其實理解起來很簡單,Java 8 中,不需要定義為 final ,但你其實可以直接把他當作 final,不要試圖修改它就行了。
即便你用內部類,現在也無需定義為 final 了。
參考 StackOverFlow 鏈接: http://stackoverflow.com/questions/4732544/why-are-only-final-variables-accessible-in-anonymous-class
Default Methods
由於歷史原因,像是類似 Collection 這種接口,如果進行添加接口的話,那將會造成之前的代碼出錯。
Java 想了一個一勞永逸的方法解決這個問題, 使用 default 修飾符來提供默認的實現
比如 Collection 接口的源代碼:
default void remove() { throw new UnsupportedOperationException("remove"); }
當沒有 override remove 這個方法是,調用的時候返回 UnsupportedOperationException 錯誤。
Static Methods in Interfaces
Java 8 中,你可以在接口中添加靜態方法了。 可能與你想的不太一樣,但是呢,為了方便起見,現在 interface 可以有靜態方法了。
參考鏈接:
- JSR 335: Lambda Expressions for the JavaTM Programming Language
- Java 8 新特性概述
- Lambda Expressions in Java 8

作者:AlexZhao
鏈接:https://www.jianshu.com/p/85affe38ce5c
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。