JDK 8新特性—Lambda表達(dá)式

Lambda表達(dá)式

簡(jiǎn)介:

Lambda表達(dá)式本質(zhì)上是一種匿名方法锤灿,能將該方法作為參數(shù)傳入其他方法中甜紫,用法上與一個(gè)只需實(shí)現(xiàn)一個(gè)方法的匿名內(nèi)部類很相似揽碘,Lambda表達(dá)式能完全取代這種匿名內(nèi)部類的位置蝉娜,且擁有著更簡(jiǎn)潔的寫(xiě)法和更好的可讀性丙者。

用法:
//  ( /*參數(shù)列表*/ )->{ /*具體實(shí)現(xiàn)*/ }
public class MyClass {
    String s="OutString";
    public static void main(String[] args){
        //  比較常用的進(jìn)程的定義
        // 匿名內(nèi)部類的寫(xiě)法
        new Thread(new Runnable() {
            @Override
            public void run() {
                //具體實(shí)現(xiàn)
                s="change";    
                // 使用外部成員時(shí)花墩,會(huì)隱式地將其指定為final類型悬秉,無(wú)法更改其值,故該部分錯(cuò)誤
            }
        }).start();

        // Lambda表達(dá)式
        new Thread(()->{
            //具體實(shí)現(xiàn)
        }).start();

        //需要參數(shù)的情況冰蘑,在此定義一個(gè)Button button=new Button();
        button.setOnClickListener((v)->{    //能識(shí)別形參的參數(shù)類型和泌,多個(gè)參數(shù)用逗號(hào)隔開(kāi)
            //具體實(shí)現(xiàn)
        });

        test(()->s);    //當(dāng)只有一行代碼時(shí)不需要“{}”,且能自動(dòng)識(shí)別返回類型
    }
    public static void test(testClass s){}
}
interface testClass{
    String m();
}

Lambda表達(dá)式與匿名類的區(qū)別:

如果只是想知道什么場(chǎng)合祠肥、怎么使用Lambda表達(dá)式武氓,上面的部分就足夠了,接下來(lái)將對(duì)Java是如何具體實(shí)現(xiàn)Lambda表達(dá)式和匿名內(nèi)部類做出介紹仇箱,會(huì)發(fā)現(xiàn)县恕,兩者有著本質(zhì)的不同。

靜態(tài)類型語(yǔ)言與動(dòng)態(tài)類型語(yǔ)言

Java是靜態(tài)類型語(yǔ)言剂桥,舉個(gè)栗子:

obj.println("Hello world");

顯然忠烛,這行代碼需要一個(gè)具體的上下文才有討論的意義,假設(shè)它在Java語(yǔ)言中权逗,并且變量obj的類型為java.io.PrintStream美尸,那obj的值就必須是PrintStream的子類(實(shí)現(xiàn)了PrintStream接口的類)才是合法的冤议。否則,哪怕obj擁有一個(gè)println(String)方法师坎,但與PrintStream接口沒(méi)有繼承關(guān)系恕酸,代碼依然不能運(yùn)行——因?yàn)轭愋蜋z查不合法。
  但是相同的代碼在動(dòng)態(tài)類型語(yǔ)言ECMAScript(JavaScript)中情況則不一樣屹耐,無(wú)論obj具體是何種類型,只要這種類型的定義中確實(shí)包含有println(String)方法椿猎,那方法調(diào)用便可成功惶岭。
  這種差別產(chǎn)生的原因是Java語(yǔ)言在編譯期間卻已將println(String)方法完整的符號(hào)引用(本例中為一項(xiàng)CONSTANT_InterfaceMethodref_info常量)生成出來(lái),作為方法調(diào)用指令的參數(shù)存儲(chǔ)到Class文件中犯眠,例如下面這個(gè)例子:

public void print(){
    PrintStream printStream=System.out;
    printStream.println("Hello world");
}
//對(duì)應(yīng)的.class文件按灶,在命令行中使用“javap -c *.class”命令查看
public void print();
  Code:
     0: getstatic     #2  // Field java/lang/System.out:Ljava/io/PrintStream;
     3: astore_1
     4: aload_1
     5: ldc           #3  // String Hello world
     7: invokevirtual #4   // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     10: return

這個(gè)符號(hào)引用(Code的“7: invokevirtual ……”部分)包含了此方法定義在哪個(gè)具體類型之中、方法的名字以及參數(shù)順序筐咧、參數(shù)類型和方法返回值等信息鸯旁,通過(guò)這個(gè)符號(hào)引用,虛擬機(jī)就可以翻譯出這個(gè)方法的直接引用(譬如方法內(nèi)存地址或者其他實(shí)現(xiàn)形式)量蕊。而在ECMAScript等動(dòng)態(tài)類型語(yǔ)言中铺罢,變量obj本身是沒(méi)有類型的,變量obj的值才具有的類型残炮,編譯時(shí)候最多只能確定方法名稱韭赘、參數(shù)、返回值這些信息势就,而不會(huì)去確定方法所在的具體類型(方法接收者不固定)泉瞻。“變量無(wú)類型而變量值才有類型”這個(gè)特點(diǎn)也是動(dòng)態(tài)類型語(yǔ)言的一個(gè)重要特征苞冯。

Java的動(dòng)態(tài)語(yǔ)言實(shí)現(xiàn)

JDK 7時(shí)Java在虛擬機(jī)層面提供了動(dòng)態(tài)類型的直接支持:invokedynamic和java.lang.invoke包袖牙,為L(zhǎng)ambda表達(dá)式的出現(xiàn)奠定了基礎(chǔ)。
  在此對(duì)invokedynamic指令做簡(jiǎn)單介紹:每一處含有invokedynamic指令的位置都被稱作“動(dòng)態(tài)調(diào)用點(diǎn)(Dynamic Call Site)”舅锄,這條指令的第一個(gè)參數(shù)不再是代表方法符號(hào)引用的CONSTANT_Methodref_info常量鞭达,而是變?yōu)镴DK 7新加入的CONSTANT_InvokeDynamic_info常量,從這個(gè)新常量中可以得到3項(xiàng)信息:引導(dǎo)方法(Bootstrap Method皇忿,此方法存放在新增的BootstrapMethods屬性中)碉怔、方法類型(MethodType)和名稱。引導(dǎo)方法是有固定的參數(shù)禁添,并且返回值是java.lang.invoke.CallSite對(duì)象撮胧,這個(gè)代表真正要執(zhí)行的目標(biāo)方法調(diào)用。根據(jù)CONSTANT_InvokeDynamic_info常量中提供的信息老翘,虛擬機(jī)可以找到并且執(zhí)行引導(dǎo)方法芹啥,從而獲得一個(gè)CallSite對(duì)象锻离,最終調(diào)用要執(zhí)行的目標(biāo)方法上。

invokedynamic #123, 0 // InvokeDynamic #0:testMethod:(Ljava/lang/String;)V   
//“#123”表示從常量池第123項(xiàng)獲取常量(CONSTANT_InvokeDynamic_info常量)
//0表示BootstrapMethods屬性表第0項(xiàng)
//其內(nèi)容則是“testMethod:(Ljava/lang/String;)V”
Lambda表達(dá)式的本質(zhì)

Java無(wú)法將方法作為參數(shù)傳遞給其他方法墓怀,因此大多數(shù)情況下會(huì)定義一個(gè)包含一個(gè)抽象方法的接口用來(lái)作為方法的載體汽纠。Lambda表達(dá)式彌補(bǔ)了這一點(diǎn),Lambda表達(dá)式的本質(zhì)是一個(gè)抽象方法傀履,可以將其作為參數(shù)傳遞虱朵。

public class MyClass {
    public void print (){
        new Thread(()-> {}).start();    //使用Lambda表達(dá)式
    }
}
//其對(duì)應(yīng)的字節(jié)碼
public class com.example.MyClass {
  public com.example.MyClass();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void print();
    Code:
       0: new           #2                  // class java/lang/Thread
       3: dup
       4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
       9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      12: invokevirtual #5                  // Method java/lang/Thread.start:()V
      15: return
}
4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;

此為L(zhǎng)ambda表達(dá)式對(duì)應(yīng)的字節(jié)碼,可以看到這里調(diào)用run方法钓账,返回java.lang.Runnable類型的對(duì)象碴犬,然后以此構(gòu)建Thread對(duì)象。

匿名內(nèi)部類與Lambda表達(dá)式的對(duì)比

觀察匿名內(nèi)部類的實(shí)現(xiàn):

public class MyClass {
    public void print (){
        new Thread(new Runnable() {
            @Override
            public void run() {}
        }).start();
    }
}
//MyClass.class其對(duì)應(yīng)的字節(jié)碼
public class com.example.MyClass {
  public com.example.MyClass();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void print();
    Code:
       0: new           #2                  // class java/lang/Thread
       3: dup
       4: new           #3                  // class com/example/MyClass$1
       7: dup
       8: aload_0
       9: invokespecial #4                  // Method com/example/MyClass$1."<init>":(Lcom/example/MyClass;)V
      12: invokespecial #5                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      15: invokevirtual #6                  // Method java/lang/Thread.start:()V
      18: return
}
//MyClass$1.class其對(duì)應(yīng)的字節(jié)碼
class com.example.MyClass$1 implements java.lang.Runnable {
  final com.example.MyClass this$0;

  com.example.MyClass$1(com.example.MyClass);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Lcom/example/MyClass;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

  public void run();
    Code:
       0: return
}

從中可以看出梆暮,匿名內(nèi)部類首先構(gòu)造了一個(gè)MyClass$1類的對(duì)象(該類繼承Runnable接口)服协,然后使用該對(duì)象構(gòu)造Thread對(duì)象,事實(shí)上啦粹,在MyClass.class文件所在的文件夾中同時(shí)存在MyClass$1.class文件偿荷。由此可看出,匿名內(nèi)部類的實(shí)現(xiàn)就是由編譯器構(gòu)建一個(gè)繼承對(duì)應(yīng)接口的類唠椭。
  由此可得知Lambda表達(dá)式是方法跳纳,匿名內(nèi)部類是類,明白了這些贪嫂,很多事情就簡(jiǎn)單了棒旗,如:兩者如何接受外部類變量和外部方法變量?
  Lambda以方法參數(shù)的形式接收:

public class MyClass {
    String classString="Hellow";    //外部類變量
    public void print (){
        String functionString="World";    //外部方法變量
        new Thread(()->{
            String a=classString+functionString;
        }).start();
    }
}
//對(duì)應(yīng)字節(jié)碼
  public com.example.MyClass();
    Code:
       ……

  public void print();
    Code:
       ……
       9: invokedynamic #6,  0              // InvokeDynamic #0:run:(Lcom/example/MyClass;Ljava/lang/String;)Ljava/lang/Runnable;
      //run方法多了兩個(gè)參數(shù)撩荣,一個(gè)指向外部類铣揉,一個(gè)指向外部方法中的String變量
      14: invokespecial #7                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      17: invokevirtual #8                  // Method java/lang/Thread.start:()V
      20: return
}

內(nèi)部類則是將變量添加入自身屬性列表:

class com.example.MyClass$1 implements java.lang.Runnable {
  final java.lang.String val$functionString;    
  //外部方法中的String變量對(duì)應(yīng)的常量,內(nèi)部使用時(shí)才存在

  final com.example.MyClass this$0;    
  //指向MyClass的常量一直存在餐曹,不管內(nèi)部有沒(méi)有使用外部類的屬性

  com.example.MyClass$1(com.example.MyClass, java.lang.String);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Lcom/example/MyClass;
       5: aload_0
       6: aload_2
       7: putfield      #2                  // Field val$functionString:Ljava/lang/String;
      10: aload_0
      11: invokespecial #3                  // Method java/lang/Object."<init>":()V
      14: return

  public void run();
    Code:
       ……
      28: return
}

對(duì)比之前方法體為空的字節(jié)碼可以看出逛拱,內(nèi)部類會(huì)一直有一個(gè)指向外部類的指針,而Lambda表達(dá)式只有在使用到外部類的屬性時(shí)台猴,才會(huì)接收該參數(shù)朽合。

Q&A:

Lambda表達(dá)式和匿名類均能直接訪問(wèn)外部方法的局部變量和外部類的屬性,但訪問(wèn)時(shí)卻隱式的指定所訪問(wèn)的成員是final(訪問(wèn)外部類的屬性是通過(guò)指向外部類的指針完成的饱狂,該指針是final類型)曹步,為什么?

匿名內(nèi)部類訪問(wèn)外部成員時(shí)休讳,會(huì)將所訪問(wèn)的成員復(fù)制到類內(nèi))讲婚,其原因是所訪問(wèn)的成員與匿名內(nèi)部類生命周期的不同,當(dāng)方法執(zhí)行完畢后俊柔,局部變量就會(huì)被清理筹麸,但匿名類可能依舊存在活合,因此需要將其復(fù)制到內(nèi)部。為了保證復(fù)制品與原始變量的一致物赶,將其指定為final類型(Lambda表達(dá)式也是同樣的道理)白指。JDK 8之后,檢測(cè)條件寬松了許多酵紫,只要所使用的屬性在之后的代碼中告嘲,其值不會(huì)改變即可,不一定非要指定為final奖地,但在JDK 8之前橄唬,外部方法中的局部變量需定義為final。

參考資料:

周志明.深入理解Java虛擬機(jī)——JVM高級(jí)特性與最佳實(shí)踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鹉动,一起剝皮案震驚了整個(gè)濱河市轧坎,隨后出現(xiàn)的幾起案子宏邮,更是在濱河造成了極大的恐慌泽示,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜜氨,死亡現(xiàn)場(chǎng)離奇詭異械筛,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)飒炎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)埋哟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人郎汪,你說(shuō)我怎么就攤上這事赤赊。” “怎么了煞赢?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵抛计,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我照筑,道長(zhǎng)吹截,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任凝危,我火速辦了婚禮波俄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛾默。我一直安慰自己懦铺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布支鸡。 她就那樣靜靜地躺著阀趴,像睡著了一般昏翰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刘急,一...
    開(kāi)封第一講書(shū)人閱讀 51,578評(píng)論 1 305
  • 那天棚菊,我揣著相機(jī)與錄音,去河邊找鬼叔汁。 笑死统求,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的据块。 我是一名探鬼主播码邻,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼另假!你這毒婦竟也來(lái)了像屋?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤边篮,失蹤者是張志新(化名)和其女友劉穎己莺,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體戈轿,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凌受,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了思杯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胜蛉。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖色乾,靈堂內(nèi)的尸體忽然破棺而出誊册,到底是詐尸還是另有隱情,我是刑警寧澤暖璧,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布案怯,位于F島的核電站,受9級(jí)特大地震影響漆撞,放射性物質(zhì)發(fā)生泄漏殴泰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一浮驳、第九天 我趴在偏房一處隱蔽的房頂上張望悍汛。 院中可真熱鬧,春花似錦至会、人聲如沸离咐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宵蛀。三九已至昆著,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間术陶,已是汗流浹背凑懂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梧宫,地道東北人接谨。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像塘匣,于是被迫代替她去往敵國(guó)和親脓豪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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

  • 本文是對(duì) Brian Goetz 的 State of Lambda 一文的翻譯 為什么要翻譯這個(gè)系列笤闯? andr...
    aaron688閱讀 3,886評(píng)論 4 31
  • 簡(jiǎn)介 概念 Lambda 表達(dá)式可以理解為簡(jiǎn)潔地表示可傳遞的匿名函數(shù)的一種方式:它沒(méi)有名稱,但它有參數(shù)列表辣垒、函數(shù)主...
    劉滌生閱讀 3,202評(píng)論 5 18
  • 前言 人生苦多望侈,快來(lái) Kotlin 印蔬,快速學(xué)習(xí)Kotlin勋桶! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,209評(píng)論 9 118
  • 在這家公司工作了兩年侥猬,從去年的四月份之前的同事離職我就發(fā)現(xiàn)公司的氛圍變了例驹,在6月份之后,我更是想離職了退唠。于是在空閑...
    第八族閱讀 341評(píng)論 0 0
  • 后來(lái)鹃锈,再回憶起左琮的時(shí)候,馬格格總會(huì)想起朋友的話:“我覺(jué)得你們關(guān)系肯定不一般瞧预,他看著你時(shí)屎债,眼睛里有光」赣停” 不過(guò)盆驹,馬...
    駱駝舅舅閱讀 622評(píng)論 3 1