Java8新特性第1章(Lambda表達(dá)式)

圖片來自網(wǎng)絡(luò)

轉(zhuǎn)載請注明出處:http://www.lxweimin.com/p/6e400da4a239


在介紹Lambda表達(dá)式之前,我們先來看只有單個(gè)方法的Interface(通常我們稱之為回調(diào)接口):

public interface OnClickListener {
    void onClick(View v);
}

我們是這樣使用它的:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        v.setText("lalala");
    }
});

這種回調(diào)模式在各種框架中非常流行,但是像上面這樣的匿名內(nèi)部類并不是一個(gè)好的選擇,因?yàn)椋?/p>

  • 語法冗余;
  • 匿名內(nèi)部類中的this指針和變量容易產(chǎn)生誤解;
  • 無法捕獲非final局部變量;
  • 非靜態(tài)內(nèi)部類默認(rèn)持有外部類的引用,部分情況下會(huì)導(dǎo)致外部類無法被GC回收,導(dǎo)致內(nèi)存泄露。

令人高興的是Java8為我們帶來了Lambda,下面我們看看利用Lambda如何實(shí)現(xiàn)上面的功能:

button.setOnClickListener(v -> v.setText("lalala"));

怎么樣?!五行代碼用一行就搞定了?。。?/p>

在這里補(bǔ)充個(gè)概念函數(shù)式接口;前面提到的OnClickListener接口只有一個(gè)方法,Java中大多數(shù)回調(diào)接口都有這個(gè)特征:比如Runnable和Comparator;我們把這些只擁有一個(gè)方法的接口稱之為函數(shù)式接口

1.Lambda表達(dá)式

匿名內(nèi)部類最大的問題在于其冗余的語法,比如前面的OnClickListener中五行代碼僅有一行是在執(zhí)行任務(wù)。Lambda表達(dá)式是匿名方法,前面我們也看到了它用極其輕量的語法解決了這一問題。

下面給大家看幾個(gè)Lambda表達(dá)式的例子:

(int x, int y) -> x + y                      //接收x和y兩個(gè)整形參數(shù)并返回他們的和
() -> 66                                     //不接收任何參數(shù)直接返回66
(String name) -> {System.out.println(name);} //接收一個(gè)字符串然后打印出來
(View view) -> {view.setText("lalala");}     //接收一個(gè)View對象并調(diào)用setText方法

Lambda表達(dá)式語法由參數(shù)列表、->函數(shù)體組成。函數(shù)體既可以是一個(gè)表達(dá)式也可以是一個(gè)代碼塊。

  • 表達(dá)式:表達(dá)式會(huì)被執(zhí)行然后返回結(jié)果。它簡化掉了return關(guān)鍵字。
  • 代碼塊:顧名思義就是一坨代碼,和普通方法中的語句一樣。

2.目標(biāo)類型

通過前面的例子我們可以看到,lambda表達(dá)式?jīng)]有名字,那我們怎么知道它的類型呢?答案是通過上下文推導(dǎo)而來的。例如,下面的表達(dá)式的類型是OnClickListener

OnClickListener listener = (View v) -> {v.setText("lalala");};

這就意味著同樣的lambda表達(dá)式在不同的上下文里有不同的類型

Runnable runnable = () -> doSomething();  //這個(gè)表達(dá)式是Runnable類型的
Callback callback = () -> doSomething();  //這個(gè)表達(dá)式是Callback類型的

編譯器利用lambda表達(dá)式所在的上下文所期待的類型來推導(dǎo)表達(dá)式的類型,這個(gè)被期待的類型被稱為目標(biāo)類型。lambda表達(dá)式只能出現(xiàn)在目標(biāo)類型函數(shù)式接口的上下文中。

Lambda表達(dá)式的類型和目標(biāo)類型的方法簽名必須一致,編譯器會(huì)對此做檢查,一個(gè)lambda表達(dá)式要想賦值給目標(biāo)類型T則必須滿足下面所有的條件:

  • T是一個(gè)函數(shù)式接口
  • lambda表達(dá)式的參數(shù)必須和T的方法參數(shù)在數(shù)量、類型和順序上一致(一一對應(yīng))
  • lambda表達(dá)式的返回值必須和T的方法的返回值一致或者是它的子類
  • lambda表達(dá)式拋出的異常和T的方法的異常一致或者是它的子類

由于目標(biāo)類型是知道lambda表達(dá)式的參數(shù)類型,所以我們沒必要把已知的類型重復(fù)一遍。也就是說lambda表達(dá)式的參數(shù)類型可以從目標(biāo)類型獲?。?/p>

//編譯器可以推導(dǎo)出s1和s2是String類型
Comparator<String> c = (s1, s2) -> s1.compareTo(s2);
//當(dāng)表達(dá)式的參數(shù)只有一個(gè)時(shí)括號也是可以省略的
button.setOnClickListener(v -> v.setText("lalala"));

ps: Java7中的泛型方法和<>構(gòu)造器也是通過目標(biāo)類型來進(jìn)行類型推導(dǎo)的,如:

List<Integer> intList = Collections.emptyList>();
List<String> strList = new ArrayList<>();

3.作用域

在內(nèi)部類中使用變量名和this非常容易出錯(cuò)。內(nèi)部類通過繼承得到的成員變量(包括來說object的)可能會(huì)把外部類的成員變量覆蓋掉,未做限制的this引用會(huì)指向內(nèi)部類自己而非外部類。

而lambda表達(dá)式的語義就十分簡單:它不會(huì)從父類中繼承任何變量,也不用引入新的作用域。lambda表達(dá)式的參數(shù)及函數(shù)體里面的變量和它外部環(huán)境的變量具有相同的語義(this關(guān)鍵字也是一樣)。

下面我們舉個(gè)栗子吧!

public class HelloLambda {

    Runnable r1 = () -> System.out.println(this);
    Runnable r2 = () -> System.out.println(toString());

    @Override
    public String toString() {
        return "Hello, lambda!";
    }

    public static void main(String[] args) {
        new HelloLambda().r1.run();  
        new HelloLambda().r2.run();
    }
}

上面的代碼最終會(huì)打印兩個(gè)Hello, lambda!,與之相類似的內(nèi)部類則會(huì)打印出類似HelloLambda$1@32a890HelloLambda$1@6b32098這種出乎意料的字符串。

總結(jié):基于詞法作用域的理念,lambda表達(dá)式不可以掩蓋任何其所在上下文的局部變量。

4.變量捕獲

在Java7中,編譯器對內(nèi)部類中引用的外部變量(即捕獲的變量)要求非常嚴(yán)格:如果捕獲的變量沒有被聲明為final就會(huì)產(chǎn)生一個(gè)編譯錯(cuò)誤。但是在Java8中放寬了這一限制--對于lambda表達(dá)式和內(nèi)部類,允許在其中捕獲那些符合有效只讀的局部變量(如果一個(gè)局部變量在初始化后從未被修改過,那么它就是有效只讀)。

Runnable getRunnable(String name){
    String hello = "hello";
    return () -> System.out.println(hello+","+name);
}

對于this的引用以及通過this對未限定字段的引用和未限定方法的調(diào)用本質(zhì)上都屬于使用final局部變量。包含此類引用的lambda表達(dá)式相當(dāng)于捕獲了this實(shí)例。在其他情況下,lambda對象不會(huì)保留任何對this的應(yīng)用。

這個(gè)特性對內(nèi)存管理是極好的:要知道在java中一個(gè)非靜態(tài)內(nèi)部類會(huì)默認(rèn)持有外部類實(shí)例的強(qiáng)引用,這往往會(huì)造成內(nèi)存泄露。而在lambda表達(dá)式中如果沒有捕獲外部類成員則不會(huì)保留對外部類實(shí)例的引用。

不過盡管Java8放寬了對捕獲變量的語法限制,但試圖修改捕獲變量的行為是被禁止的,比如下面這個(gè)例子就是非法的:

int sum  = 0;
list.forEach(i -> {sum += i;});

為什么要禁止這種行為呢?因?yàn)檫@樣的lambda表達(dá)式很容易引起race condition

lambda表達(dá)式不支持修改捕獲變量的另外一個(gè)原因是我們可以使用更好的方式來實(shí)現(xiàn)同樣的效果:使用規(guī)約(condition)。java.util.stream包提供了各種規(guī)約操作,關(guān)于Java8中的Stream API我們放到下一章介紹。

5.方法引用

lambda表達(dá)式允許我們定義一個(gè)匿名方法,并以函數(shù)式接口的方式使用它。Java8能夠在已有的方法上實(shí)現(xiàn)同樣的特性。

方法引用和lambda表達(dá)式擁有相同的特性(他們都需要一個(gè)目標(biāo)類型,并且需要被轉(zhuǎn)化為函數(shù)式接口的實(shí)例),不過我們不需要為方法引用提供方法體,我們可以直接通過方法名引用已有方法。

以下面的代碼為例,假設(shè)我們要按照userName排序

class User{

    private String userName;

    public String getUserName() {
        return userName;
    }
    ...
}

List<User> users = new ArrayList<>();
Comparator<User> comparator = Comparator.comparing(u -> u.getUserName());
Collections.sort(users, comparator);

我們可以用方法引用替換上面的lambda表達(dá)式

Comparator<User> comparator = Comparator.comparing(User::getUserName);

這里的User::getUserName被看做是lambda表達(dá)式的簡寫形式。盡管方法引用不一定會(huì)把代碼變得更緊湊,但它擁有更明確的語義--如果我們想要調(diào)用的方法擁有一個(gè)名字,那么我們就可以通過方法名調(diào)用它。

方法引用有很多種,它們的語法如下:

  • 靜態(tài)方法引用:ClassName::methodName
  • 實(shí)例上的實(shí)例方法引用:instanceReference::methodName
  • 超類上的實(shí)例方法引用:super::methodName
  • 類型上的實(shí)例方法引用:ClassName::methodName
  • 構(gòu)造方法引用:Class::new
  • 數(shù)組構(gòu)造方法引用:TypeName[]::new
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,995評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容