Java Lambda 表達(dá)式(又名閉包(Closure)/匿名函數(shù)) 筆記

本文首發(fā)于 blog.zhaochunqi.com 轉(zhuǎn)載請注明 blog.zhaochunqi.com

根據(jù)JSR 335, Java 終于在 Java 8 中引入了 Lambda 表達(dá)式。也稱之為閉包或者匿名函數(shù)。

http://harchiko.qiniudn.com/Lambda%20Expression%20Java%208.png

JSR 335

所謂的 JSR (Java Specification Requests) 全稱叫做 Java 規(guī)范提案。簡單來說就是向 Java 社區(qū)提交新的 API 或 服務(wù) 請求的提案。這些提案將作為 Java 社區(qū)進(jìn)行 Java 語言開發(fā)的需求,引導(dǎo)著開發(fā)的方向。

JSR 335 的提案內(nèi)容摘要如下:

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

也就是如下幾點:

  1. 支持 lambda 表達(dá)式。
  2. 支持 SAM conversion 用來向前兼容。
  3. 方法引用 Method References
  4. Virtual Extension Methods

在 Java 8 中,以上均已經(jīng)實現(xiàn),以上內(nèi)容下文均有介紹。

為什么需要 Lambda 表達(dá)式?

Lambda 表達(dá)式,其實就是代碼塊。

http://harchiko.qiniudn.com/56cabf5a499ed708%202.jpg

原來怎么處理

在具體了解 lambda 之前,我們先往后退一步,看看之前我們是如何處理這些代碼塊的!

例子一

當(dāng)決定在單獨的線程運(yùn)行某程序時,你這樣做的

class Worker implements Runnable {
     public void run() {
        for (int i = 0; i < 1000; i++)
           doWork();
     }
     ...
  }

這樣執(zhí)行:

Worker w = new Worker();
new Thread(w).start();

Worker 中包含了你要執(zhí)行的代碼塊。

例子二

如果你想實現(xiàn)根據(jù)字符串長度大小來排序,而不是默認(rèn)的字母順序,你可以自己來實現(xiàn)一個 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();
    }
});

上面代碼有什么問題呢?

http://harchiko.qiniudn.com/c718cee7.jpg

它們都太復(fù)雜了??!

上述例子都是在某個類中實現(xiàn)某個接口,然后傳遞到另外一個方法中作為參數(shù),然后用來執(zhí)行。

但是本質(zhì)上,他們要傳遞的就是接口中那一個方法的實現(xiàn)而已啊!有必要先創(chuàng)建類,再實例化,再傳遞給調(diào)用的位置嗎?

因為 Java 是純面向?qū)ο蟮恼Z言,像其他語言那樣隨隨便便傳個方法過來,那可不行,必須要這樣。

在其他語言中你可能可以,但是,在Java 中,不可以。

http://harchiko.qiniudn.com/56cabf7011ab6750.jpg

Java 設(shè)計人員為了 Java 的簡潔跟連貫性,一直拒絕為Java添加這種功能。(這也是我喜歡Java而不喜歡Python的原因?。。。?

經(jīng)過多年的努力,開發(fā)人員終于找到了符合 Java 編程習(xí)慣的 Lambda 表達(dá)式!

Lambda 表達(dá)式語法(Syntax)

考慮下前面的例子:

Integer.compare(first.length(), second.length())

first和second都是 String 類型,Java 是強(qiáng)類型的語言,必須指定類型:

(String first, String second)
     -> Integer.compare(first.length(), second.length())
http://harchiko.qiniudn.com/14365393725281065.jpg

看到?jīng)]有!第一個 Lambda 表達(dá)式誕生了!!輸入、輸出簡潔明了!

為什么叫 Lambda 呢,這個很多年以前,有位邏輯學(xué)家想要標(biāo)準(zhǔn)化的表示一些可以被計算的數(shù)學(xué)方程(實際上存在,但是很難被表示出來),他就用 ? 來表示。

重新介紹一下 Java 中 Lambda 表達(dá)式的格式:

(參數(shù)) -> 表達(dá)式

多返回值

如果計算的結(jié)果并不由一個單一的表達(dá)式返回(換言之,返回值存在多種情況),使用“{}",然后明確指定返回值。

(String first, String second) -> {
     if (first.length() < second.length()) return -1;
     else if (first.length() > second.length()) return 1;
     else return 0;
}

無參數(shù)

如果沒有參數(shù),則 "()"中就空著。

() -> { for (int i = 0; i < 1000; i++) doWork(); }

省略

如果參數(shù)的類型可以被推斷出,則可以直接省略

Comparator<String> comp
     = (first, second) // Same as (String first, String second)
        -> Integer.compare(first.length(), second.length());

這里,first和second可以被推斷出是 String 類型,因為 是一個 String 類型的 Comparator。

如果單個參數(shù)可以被推斷出,你連括號都可以省略:

EventHandler<ActionEvent> listener = event ->
     System.out.println("Thanks for clicking!");
        // Instead of (event) -> or (ActionEvent event) ->

修飾符

你可以像對待其他方法一樣,annotation,或者 使用 final 修飾符

(final String name) -> ...
    (@NonNull String name) -> ...

永遠(yuǎn)不要定義 result 的類型,lambda 表達(dá)式總是從上下文中推斷出來的:

(String first, String second) -> Integer.compare(first.length(), second.length())

注意

注意,在lambda 表達(dá)式中,某些分支存在返回值,某些不存在返回值這樣的情況是不允許的。
(int x) -> { if (x >= 0) return 1; }這樣是非法的。

函數(shù)式接口(Functional Interfaces/SAM)

要介紹 Java 中 lambda 表達(dá)式的實現(xiàn),需要知道什么是 函數(shù)式接口。

什么叫作函數(shù)式接口呢(SAM)?

函數(shù)式接口指的是只定義了唯一的抽象方法的接口(除了隱含的Object對象的公共方法), 因此最開始也就做SAM類型的接口(Single Abstract Method)。

Lambda 表達(dá)式向前兼容這些接口。

Comparable

舉個例子 Array.sort:

Arrays.sort(words,
     (first, second) -> Integer.compare(first.length(), second.length()));

Array.sort() 方法收到一個實現(xiàn)了 Comparable<String> 接口的實例。

其實可以把 Lambda 表達(dá)式想象成一個方法,而非一個對象,一個可以傳入一個接口的方法。

OnClickListener

再舉個例子

button.setOnClickListener(event ->
     System.out.println("Thanks for clicking!"));

你看,是不是更易讀了呢?

Lambda 表達(dá)式能夠向前兼容這些 interfaces, 太棒了! 那 Lambda 表達(dá)式還能干什么呢?

實際上,將函數(shù)式接口轉(zhuǎn)變成 lambda 表達(dá)式是你在 Java 中唯一能做的事情。

http://harchiko.qiniudn.com/20150930185659_eMZyN.jpeg

Why ???!

在其他的語言中,你可以定義一些方便的方法類型,但在 Java 中,你甚至不能將一個Lambda表達(dá)式賦值給類型為 Object 的變量,因為 Object 變量不是一個 Functional Interface。

Java 的設(shè)計者們堅持使用熟悉的 interface 概念而不是為其引入新的 方法類型。

(這里我還要為設(shè)計者點贊!謹(jǐn)慎的設(shè)計,一方面降低了初學(xué)者的門檻,一方面方便了高級用戶的使用。對比 python2和 python3,升級的不兼容讓很多人一直停留在 python2)

Method References

能不能再簡潔一點?有的時候我們所要做的事情不過是調(diào)用其他類中方法來處理事件。

button.setOnClickListener(event -> System.out.println(event));

如果這樣呢?

button.setOnAction(System.out::println);

表達(dá)式 System.out::println 屬于一個方法引用(method reference), 相當(dāng)于 lambda 表達(dá)式 x -> System.out.println(x)

http://harchiko.qiniudn.com/20151220232425_nWH23.jpeg

再舉個例子,如果你想對字符串不管大小寫進(jìn)行排序,就可以這樣寫!

Arrays.sort(strings, String::compareToIgnoreCase)

如上所見 ::操作符將方法名與實例或者類分隔開。總體來說,又如下的規(guī)則:

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod

值得指出的是, thissuper關(guān)鍵字可以在其中使用:

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();
 }
}

構(gòu)造方法引用 Constructor References

跟上一個差不多,畢竟構(gòu)造方法 也是方法?。?!不過方法名字為 new 。

但是!這個構(gòu)造方法引用有一個牛逼的地方!

你知道 Array 是不能使用范型的對吧?。ㄊ裁矗悴恢??看看這里 http://stackoverflow.com/questions/2927391/whats-the-reason-i-cant-create-generic-array-types-in-java),你沒有辦法創(chuàng)建一個類型為 T 的 Array 。 new T[n] 將會被覆蓋為 new Object[n]。

假設(shè)我們想要一個包含 buttons 的 Array。Stream interface 可以返回一個 Object array。

Object[] buttons = stream.toArray();

不不不,我們可不想要 Object。Stream 庫使用 構(gòu)造方法引用解決了這個問題:

Button[] buttons = stream.toArray(Button[]::new);
http://harchiko.qiniudn.com/Screen%20Shot%202016-11-16%20at%204.30.23%20AM.png

變量作用域

注意到我們在題目中寫著 閉包(closure),實際上,閉包的定義是: 引用了自由變量的函數(shù)。

在之前,如果需要在匿名類的內(nèi)部引用外部變量,需要將外部變量定義為 final ,現(xiàn)在有了 lambda 表達(dá)式,你不必再這么做了。但同樣需要保證外部的自由變量不能在 lambda 表達(dá)式中被改變。

http://harchiko.qiniudn.com/56cabf5d7d6dc247.jpg!600x600.jpg

這是什么意思呢? 不需要定義為 final,也不能改?

其實理解起來很簡單,Java 8 中,不需要定義為 final ,但你其實可以直接把他當(dāng)作 final,不要試圖修改它就行了。

即便你用內(nèi)部類,現(xiàn)在也無需定義為 final 了。

參考 StackOverFlow 鏈接: http://stackoverflow.com/questions/4732544/why-are-only-final-variables-accessible-in-anonymous-class

Default Methods

由于歷史原因,像是類似 Collection 這種接口,如果進(jìn)行添加接口的話,那將會造成之前的代碼出錯。

Java 想了一個一勞永逸的方法解決這個問題, 使用 default 修飾符來提供默認(rèn)的實現(xiàn)

比如 Collection 接口的源代碼:

default void remove() {
     throw new UnsupportedOperationException("remove");
}

當(dāng)沒有 override remove 這個方法是,調(diào)用的時候返回 UnsupportedOperationException 錯誤。

Static Methods in Interfaces

Java 8 中,你可以在接口中添加靜態(tài)方法了。 可能與你想的不太一樣,但是呢,為了方便起見,現(xiàn)在 interface 可以有靜態(tài)方法了。

參考鏈接:

  1. JSR 335: Lambda Expressions for the JavaTM Programming Language
  2. Java 8 新特性概述
  3. Lambda Expressions in Java 8
http://harchiko.qiniudn.com/44577950_p0.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容