Java 8:一文掌握 Lambda 表達式

本文將介紹 Java 8 新增的 Lambda 表達式恬砂,包括 Lambda 表達式的常見用法以及方法引用的用法,并對 Lambda 表達式的原理進行分析匪凡,最后對 Lambda 表達式的優(yōu)缺點進行一個總結谨敛。

目錄

1. 概述

Java 8 引入的 Lambda 表達式的主要作用就是簡化部分匿名內(nèi)部類的寫法。

能夠使用 Lambda 表達式的一個重要依據(jù)是必須有相應的函數(shù)接口哮奇。所謂函數(shù)接口,是指內(nèi)部有且僅有一個抽象方法的接口。

Lambda 表達式的另一個依據(jù)是類型推斷機制浪规。在上下文信息足夠的情況下,編譯器可以推斷出參數(shù)表的類型探孝,而不需要顯式指名笋婿。

2. 常見用法

2.1 無參函數(shù)的簡寫

無參函數(shù)就是沒有參數(shù)的函數(shù),例如 Runnable 接口的 run() 方法顿颅,其定義如下:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

在 Java 7 及之前版本缸濒,我們一般可以這樣使用:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
        System.out.println("Jimmy");
    }
}).start();

從 Java 8 開始,無參函數(shù)的匿名內(nèi)部類可以簡寫成如下方式:

() -> {
    執(zhí)行語句
}

這樣接口名和函數(shù)名就可以省掉了元镀。那么绍填,上面的示例可以簡寫成:

new Thread(() -> {
    System.out.println("Hello");
    System.out.println("Jimmy");
}).start();

當只有一條語句時,我們還可以對代碼塊進行簡寫栖疑,格式如下:

() -> 表達式

注意這里使用的是表達式讨永,并不是語句,也就是說不需要在末尾加分號遇革。

那么卿闹,當上面的例子中執(zhí)行的語句只有一條時,可以簡寫成這樣:

new Thread(() -> System.out.println("Hello")).start();

2.2 單參函數(shù)的簡寫

單參函數(shù)是指只有一個參數(shù)的函數(shù)萝快。例如 View 內(nèi)部的接口 OnClickListener 的方法 onClick(View v)锻霎,其定義如下:

public interface OnClickListener {
    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    void onClick(View v);
}

在 Java 7 及之前的版本,我們通尘句觯可能會這么使用:

view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        v.setVisibility(View.GONE);
    }
});

從 Java 8 開始旋恼,單參函數(shù)的匿名內(nèi)部類可以簡寫成如下方式:

([類名 ]變量名) -> {
    執(zhí)行語句
}

其中類名是可以省略的,因為 Lambda 表達式可以自己推斷出來奄容。那么上面的例子可以簡寫成如下兩種方式:

view.setOnClickListener((View v) -> {
    v.setVisibility(View.GONE);
});
view.setOnClickListener((v) -> {
    v.setVisibility(View.GONE);
});

單參函數(shù)甚至可以把括號去掉冰更,官方也更建議使用這種方式:

變量名 -> {
    執(zhí)行語句
}

那么,上面的示例可以簡寫成:

view.setOnClickListener(v -> {
    v.setVisibility(View.GONE);
});

當只有一條語句時昂勒,依然可以對代碼塊進行簡寫蜀细,格式如下:

([類名 ]變量名) -> 表達式

類名和括號依然可以省略,如下:

變量名 -> 表達式

那么戈盈,上面的示例可以進一步簡寫成:

view.setOnClickListener(v -> v.setVisibility(View.GONE));

2.3 多參函數(shù)的簡寫

多參函數(shù)是指具有兩個及以上參數(shù)的函數(shù)奠衔。例如谆刨,Comparator 接口的 compare(T o1, T o2) 方法就具有兩個參數(shù),其定義如下:

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

在 Java 7 及之前的版本归斤,當我們對一個集合進行排序時痊夭,通常可以這么寫:

List<Integer> list = Arrays.asList(1, 2, 3);
Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
});

從 Java 8 開始官册,多參函數(shù)的匿名內(nèi)部類可以簡寫成如下方式:

([類名1 ]變量名1, [類名2 ]變量名2[, ...]) -> {
    執(zhí)行語句
}

同樣類名可以省略生兆,那么上面的例子可以簡寫成:

Collections.sort(list, (Integer o1, Integer o2) -> {
    return o1.compareTo(o2);
});
Collections.sort(list, (o1, o2) -> {
    return o1.compareTo(o2);
});

當只有一條語句時,依然可以對代碼塊進行簡寫膝宁,格式如下:

([類名1 ]變量名1, [類名2 ]變量名2[, ...]) -> 表達式

此時類名也是可以省略的鸦难,但括號不能省略。如果這條語句需要返回值员淫,那么 return 關鍵字是不需要寫的合蔽。

因此,上面的示例可以進一步簡寫成:

Collections.sort(list, (o1, o2) -> o1.compareTo(o2));

最后呢介返,這個示例還可以簡寫成這樣:

Collections.sort(list, Integer::compareTo);

咦拴事,這是什么特性?這就是我們下面要講的內(nèi)容:方法引用圣蝎。

3. 方法引用

方法引用也是一個語法糖刃宵,可以用來簡化開發(fā)。

在我們使用 Lambda 表達式的時候徘公,如果“->”的右邊要執(zhí)行的表達式只是調用一個類已有的方法牲证,那么就可以用「方法引用」來替代 Lambda 表達式。

方法引用可以分為 4 類:

  • 引用靜態(tài)方法关面;
  • 引用對象的方法坦袍;
  • 引用類的方法;
  • 引用構造方法等太。

下面按照這 4 類分別進行闡述捂齐。

3.1 引用靜態(tài)方法

當我們要執(zhí)行的表達式是調用某個類的靜態(tài)方法,并且這個靜態(tài)方法的參數(shù)列表和接口里抽象函數(shù)的參數(shù)列表一一對應時缩抡,我們可以采用引用靜態(tài)方法的格式奠宜。

假如 Lambda 表達式符合如下格式:

([變量1, 變量2, ...]) -> 類名.靜態(tài)方法名([變量1, 變量2, ...])

我們可以簡寫成如下格式:

類名::靜態(tài)方法名

注意這里靜態(tài)方法名后面不需要加括號,也不用加參數(shù)瞻想,因為編譯器都可以推斷出來压真。下面我們繼續(xù)使用 2.3 節(jié)的示例來進行說明。

首先創(chuàng)建一個工具類内边,代碼如下:

public class Utils {
    public static int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
}

注意這里的 compare() 函數(shù)的參數(shù)和 Comparable 接口的 compare() 函數(shù)的參數(shù)是一一對應的。然后一般的 Lambda 表達式可以這樣寫:

Collections.sort(list, (o1, o2) -> Utils.compare(o1, o2));

如果采用方法引用的方式待锈,可以簡寫成這樣:

Collections.sort(list, Utils::compare);

3.2 引用對象的方法

當我們要執(zhí)行的表達式是調用某個對象的方法漠其,并且這個方法的參數(shù)列表和接口里抽象函數(shù)的參數(shù)列表一一對應時,我們就可以采用引用對象的方法的格式。

假如 Lambda 表達式符合如下格式:

([變量1, 變量2, ...]) -> 對象引用.方法名([變量1, 變量2, ...])

我們可以簡寫成如下格式:

對象引用::方法名

下面我們繼續(xù)使用 2.3 節(jié)的示例來進行說明和屎。首先創(chuàng)建一個類拴驮,代碼如下:

public class MyClass {
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
}

當我們創(chuàng)建一個該類的對象,并在 Lambda 表達式中使用該對象的方法時柴信,一般可以這么寫:

MyClass myClass = new MyClass();
Collections.sort(list, (o1, o2) -> myClass.compare(o1, o2));

注意這里函數(shù)的參數(shù)也是一一對應的套啤,那么采用方法引用的方式,可以這樣簡寫:

MyClass myClass = new MyClass();
Collections.sort(list, myClass::compare);

此外随常,當我們要執(zhí)行的表達式是調用 Lambda 表達式所在的類的方法時潜沦,我們還可以采用如下格式:

this::方法名

例如我在 Lambda 表達式所在的類添加如下方法:

private int compare(Integer o1, Integer o2) {
    return o1.compareTo(o2);
}

當 Lambda 表達式使用這個方法時,一般可以這樣寫:

Collections.sort(list, (o1, o2) -> compare(o1, o2));

如果采用方法引用的方式绪氛,就可以簡寫成這樣:

Collections.sort(list, this::compare);

3.3 引用類的方法

引用類的方法所采用的參數(shù)對應形式與上兩種略有不同唆鸡。如果 Lambda 表達式的“->”的右邊要執(zhí)行的表達式是調用的“->”的左邊第一個參數(shù)的某個實例方法,并且從第二個參數(shù)開始(或無參)對應到該實例方法的參數(shù)列表時枣察,就可以使用這種方法争占。

可能有點繞,假如我們的 Lambda 表達式符合如下格式:

(變量1[, 變量2, ...]) -> 變量1.實例方法([變量2, ...])

那么我們的代碼就可以簡寫成:

變量1對應的類名::實例方法名

還是使用 2.3 節(jié)的例子序目, 當我們使用的 Lambda 表達式是這樣時:

Collections.sort(list, (o1, o2) -> o1.compareTo(o2));

按照上面的說法臂痕,就可以簡寫成這樣:

Collections.sort(list, Integer::compareTo);

3.4 引用構造方法

當我們要執(zhí)行的表達式是新建一個對象,并且這個對象的構造方法的參數(shù)列表和接口里函數(shù)的參數(shù)列表一一對應時猿涨,我們就可以采用「引用構造方法」的格式握童。

假如我們的 Lambda 表達式符合如下格式:

([變量1, 變量2, ...]) -> new 類名([變量1, 變量2, ...])

我們就可以簡寫成如下格式:

類名::new

下面舉個例子說明一下。Java 8 引入了一個 Function 接口嘿辟,它是一個函數(shù)接口舆瘪,部分代碼如下:

@FunctionalInterface
public interface Function<T, R> {
    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
        // 省略部分代碼
}

我們用這個接口來實現(xiàn)一個功能,創(chuàng)建一個指定大小的 ArrayList红伦。一般我們可以這樣實現(xiàn):

Function<Integer, ArrayList> function = new Function<Integer, ArrayList>() {
    @Override
    public ArrayList apply(Integer n) {
        return new ArrayList(n);
    }
};
List list = function.apply(10);

使用 Lambda 表達式英古,我們一般可以這樣寫:

Function<Integer, ArrayList> function = n -> new ArrayList(n);

使用「引用構造方法」的方式,我們可以簡寫成這樣:

Function<Integer, ArrayList> function = ArrayList::new;

4. 自定義函數(shù)接口

自定義函數(shù)接口很容易昙读,只需要編寫一個只有一個抽象方法的接口即可召调,示例代碼:

@FunctionalInterface
public interface MyInterface<T> {
    void function(T t);
}

上面代碼中的 @FunctionalInterface 是可選的,但加上該注解編譯器會幫你檢查接口是否符合函數(shù)接口規(guī)范蛮浑。就像加入 @Override 注解會檢查是否重寫了函數(shù)一樣唠叛。

5. 實現(xiàn)原理

經(jīng)過上面的介紹,我們看到 Lambda 表達式只是為了簡化匿名內(nèi)部類書寫沮稚,看起來似乎在編譯階段把所有的 Lambda 表達式替換成匿名內(nèi)部類就可以了艺沼。但實際情況并非如此,在 JVM 層面蕴掏,Lambda 表達式和匿名內(nèi)部類其實有著明顯的差別障般。

5.1 匿名內(nèi)部類的實現(xiàn)

匿名內(nèi)部類仍然是一個類调鲸,只是不需要我們顯式指定類名,編譯器會自動為該類取名挽荡。比如有如下形式的代碼:

public class LambdaTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World");
            }
        }).start();
    }
}

編譯之后將會產(chǎn)生兩個 class 文件:

LambdaTest.class
LambdaTest$1.class

使用 javap -c LambdaTest.class 進一步分析 LambdaTest.class 的字節(jié)碼藐石,部分結果如下:

public static void main(java.lang.String[]);
  Code:
     0: new           #2                  // class java/lang/Thread
     3: dup
     4: new           #3                  // class com/example/myapplication/lambda/LambdaTest$1
     7: dup
     8: invokespecial #4                  // Method com/example/myapplication/lambda/LambdaTest$1."<init>":()V
    11: invokespecial #5                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
    14: invokevirtual #6                  // Method java/lang/Thread.start:()V
    17: return

可以發(fā)現(xiàn)在 4: new #3 這一行創(chuàng)建了匿名內(nèi)部類的對象。

5.2 Lambda 表達式的實現(xiàn)

接下來我們將上面的示例代碼使用 Lambda 表達式實現(xiàn)定拟,代碼如下:

public class LambdaTest {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello World")).start();
    }
}

此時編譯后只會產(chǎn)生一個文件 LambdaTest.class于微,再來看看通過 javap 對該文件反編譯后的結果:

public static void main(java.lang.String[]);
  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

從上面的結果我們發(fā)現(xiàn) Lambda 表達式被封裝成了主類的一個私有方法,并通過 invokedynamic 指令進行調用青自。

因此株依,我們可以得出結論:Lambda 表達式是通過 invokedynamic 指令實現(xiàn)的,并且書寫 Lambda 表達式不會產(chǎn)生新的類性穿。

既然 Lambda 表達式不會創(chuàng)建匿名內(nèi)部類勺三,那么在 Lambda 表達式中使用 this 關鍵字時,其指向的是外部類的引用需曾。

6. 優(yōu)缺點

優(yōu)點:

  • 可以減少代碼的書寫吗坚,減少匿名內(nèi)部類的創(chuàng)建,節(jié)省內(nèi)存占用呆万。
  • 使用時不用去記憶所使用的接口和抽象函數(shù)商源。

缺點:

  • 易讀性較差,閱讀代碼的人需要熟悉 Lambda 表達式和抽象函數(shù)中參數(shù)的類型谋减。
  • 不方便進行調試牡彻。

參考

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市出爹,隨后出現(xiàn)的幾起案子庄吼,更是在濱河造成了極大的恐慌,老刑警劉巖严就,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件总寻,死亡現(xiàn)場離奇詭異,居然都是意外死亡梢为,警方通過查閱死者的電腦和手機渐行,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铸董,“玉大人祟印,你說我怎么就攤上這事∷诤Γ” “怎么了蕴忆?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長悲幅。 經(jīng)常有香客問我套鹅,道長驻襟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任芋哭,我火速辦了婚禮,結果婚禮上郁副,老公的妹妹穿的比我還像新娘减牺。我一直安慰自己,他們只是感情好存谎,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布拔疚。 她就那樣靜靜地躺著,像睡著了一般既荚。 火紅的嫁衣襯著肌膚如雪稚失。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天恰聘,我揣著相機與錄音句各,去河邊找鬼。 笑死晴叨,一個胖子當著我的面吹牛凿宾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播兼蕊,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼初厚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了孙技?” 一聲冷哼從身側響起产禾,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎牵啦,沒想到半個月后亚情,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡蕾久,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年势似,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僧著。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡履因,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盹愚,到底是詐尸還是另有隱情栅迄,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布皆怕,位于F島的核電站毅舆,受9級特大地震影響西篓,放射性物質發(fā)生泄漏。R本人自食惡果不足惜憋活,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一岂津、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悦即,春花似錦吮成、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至作瞄,卻和暖如春茶宵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宗挥。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工乌庶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人契耿。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓安拟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宵喂。 傳聞我的和親對象是個殘疾皇子糠赦,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

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