前言
Java11前兩天都發(fā)布了,而自己Java8還沒搞明白,真是羞為稱自己為Java程序員。今天就讓我們來(lái)通俗易懂的聊一聊Java8中的Lambda表達(dá)式~
簡(jiǎn)單上手

通過(guò)Lamda表達(dá)式,可以變換為:
new Thread(() -> System.out.println("thread"));
針對(duì)這種實(shí)行,我們?cè)趺蠢斫饽??其?shí)很簡(jiǎn)單,上看一下上述lambda表達(dá)式的語(yǔ)法:() -> {} (): 括號(hào)就是接口方法的括號(hào),接口方法如果有參數(shù),也需要寫參數(shù)。只有一個(gè)參數(shù)時(shí),括號(hào)可以省略。-> : 分割左右部分的,沒啥好講的。{} : 要實(shí)現(xiàn)的方法體。只有一行代碼時(shí),可以不加括號(hào),可以不寫return。
不過(guò)看到這里我相信有些小伙伴已經(jīng)許意識(shí)到了,如果接口中有多個(gè)方法時(shí),那么按照上面的邏輯lambda表達(dá)式恐怕沒辦法表示了。的確是這樣,并非任何接口都支持lambda表達(dá)式。
而適用于lambda表達(dá)式的接口稱之為函數(shù)型接口。說(shuō)白了,函數(shù)型接口就是只有一個(gè)抽象方法的接口。
函數(shù)式接口
其實(shí)之前在講Lambda表達(dá)式的時(shí)候提到過(guò),所謂的函數(shù)式接口,當(dāng)然首先是一個(gè)接口,然后就是在這個(gè)接口里面只能有一個(gè)抽象方法。
這種類型的接口也稱為SAM接口,即Single Abstract Method interfaces。
1.1、函數(shù)式接口基本語(yǔ)法
它們主要用在Lambda表達(dá)式和方法引用(實(shí)際上也可認(rèn)為是Lambda表達(dá)式)上。
如定義了一個(gè)函數(shù)式接口如下:

那么就可以使用Lambda表達(dá)式來(lái)表示該接口的一個(gè)實(shí)現(xiàn)(注:JAVA 8 之前一般是用匿名類實(shí)現(xiàn)的):
GreetingService greetService1 = message -> System.out.println("Hello " + message);
1.2、FunctionalInterface注解
關(guān)于@FunctionalInterface注解Java 8為函數(shù)式接口引入了一個(gè)新注解@FunctionalInterface,主要用于編譯級(jí)錯(cuò)誤檢查,加上該注解,當(dāng)你寫的接口不符合函數(shù)式接口定義的時(shí)候,編譯器會(huì)報(bào)錯(cuò)。
正確例子,沒有報(bào)錯(cuò):

1.3、用法提醒
ERROR:接口中包含了兩個(gè)抽象方法,違反了函數(shù)式接口的定義,IDE會(huì)直接報(bào)錯(cuò)。
Tips:加不加@FunctionalInterface對(duì)于接口是不是函數(shù)式接口沒有影響,該注解知識(shí)提醒編譯器去檢查該接口是否僅包含一個(gè)抽象方法
1.4、默認(rèn)方法
函數(shù)式接口里是可以包含默認(rèn)方法,因?yàn)槟J(rèn)方法不是抽象方法,其有一個(gè)默認(rèn)實(shí)現(xiàn),所以是符合函數(shù)式接口的定義的;

1.5、靜態(tài)方法
函數(shù)式接口里是可以包含靜態(tài)方法,因?yàn)殪o態(tài)方法不能是抽象方法,是一個(gè)已經(jīng)實(shí)現(xiàn)了的方法,所以是符合函數(shù)式接口的定義的;
如下代碼不會(huì)報(bào)錯(cuò):

1.6、Object里的public方法
函數(shù)式接口里是可以包含Object里的public方法,這些方法對(duì)于函數(shù)式接口來(lái)說(shuō),不被當(dāng)成是抽象方法(雖然它們是抽象方法);因?yàn)槿魏我粋€(gè)函數(shù)式接口的實(shí)現(xiàn),默認(rèn)都繼承了Object類,包含了來(lái)自java.lang.Object里對(duì)這些抽象方法的實(shí)現(xiàn);
如下代碼不會(huì)報(bào)錯(cuò):

進(jìn)階
有了上面的基礎(chǔ),我們稍稍聊一些深入的lambda表達(dá)式。lambda表達(dá)式還有兩種簡(jiǎn)化代碼的手段,它們是方法引用、構(gòu)造引用。
方法引用是什么呢?如果我們要實(shí)現(xiàn)接口的方法與另一個(gè)方法A類似,(這里的類似是指參數(shù)類型與返回值部分相同),我們直接聲明A方法即可。也就是,不再使用lambda表達(dá)式的標(biāo)準(zhǔn)形式,改用高級(jí)形式。無(wú)論是標(biāo)準(zhǔn)形式還是高級(jí)形式,都是lambda表達(dá)式的一種表現(xiàn)形式。
Function function1 = (x) -> x;Function function2 = String::valueOf;
對(duì)比Function接口的抽象方法與String的value方法,可以看到它們是類似的。

方法引用的語(yǔ)法:
對(duì)象::實(shí)例方法類::靜態(tài)方法類::實(shí)例方法
前兩個(gè)很容易理解,相當(dāng)于對(duì)象調(diào)用實(shí)例方法,類調(diào)用靜態(tài)方法一樣。只是第三個(gè)需要特殊說(shuō)明。
Compare<Boolean> c = String::equals;
也就是“類::實(shí)例方法”的形式。
構(gòu)造引用

提煉一下構(gòu)造引用的語(yǔ)法:類名::new

變量作用域
lambda 表達(dá)式只能引用標(biāo)記了 final 的外層局部變量,這就是說(shuō)不能在 lambda 內(nèi)部修改定義在域外的局部變量,否則會(huì)編譯錯(cuò)誤。
在 Java8Tester.java 文件輸入以下代碼:
public class Java8Tester {
final static String salutation = "Hello! ";
public static void main(String args[]){
GreetingService greetService1 = message ->
System.out.println(salutation + message);
greetService1.sayMessage("Runoob");
}
interface GreetingService {
void sayMessage(String message);
}
}
執(zhí)行以上腳本,輸出結(jié)果為:
Hello! Runoob
我們也可以直接在 lambda 表達(dá)式中訪問(wèn)外層的局部變量:
public class Java8Tester {
public static void main(String args[]) {
final int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2); // 輸出結(jié)果為 3
}
public interface Converter<T1, T2> {
void convert(int i);
}
}
lambda 表達(dá)式的局部變量可以不用聲明為 final,但是必須不可被后面的代碼修改(即隱性的具有 final 的語(yǔ)義)
int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;
//報(bào)錯(cuò)信息:Local variable num defined in an enclosing scope must be final or effectively
final
在 Lambda 表達(dá)式當(dāng)中不允許聲明一個(gè)與局部變量同名的參數(shù)或者局部變量。
String first = "";
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); //編譯會(huì)出錯(cuò)