Java 8 之后推出的 Lambda 表達(dá)式開(kāi)啟了 Java 語(yǔ)言支持函數(shù)式編程(Functional Programming)的新時(shí)代穿香。
Lambda 表達(dá)式亭引,也稱為閉包(Closure),現(xiàn)在很多語(yǔ)言都支持 Lambda表達(dá)式皮获,如 C++焙蚓、C#、Swift洒宝、Objective-C 和 JavaScript 等购公。為什么 Lambda 表達(dá)式這怎么受歡迎,這是因?yàn)長(zhǎng)ambda表達(dá)式是實(shí)現(xiàn)支持函數(shù)式編程技術(shù)基礎(chǔ)雁歌。
函數(shù)式編程與面向?qū)ο缶幊逃泻艽蟮牟顒e宏浩,函數(shù)式編程將程序代碼看作數(shù)學(xué)中的函數(shù),函數(shù)本身作為另一個(gè)函數(shù)的參數(shù)或返回值将宪,即高階函數(shù)绘闷。而面向?qū)ο缶幊淌前凑照鎸?shí)世界客觀事物的自然規(guī)律進(jìn)行分析橡庞,客觀世界中存在什么樣的實(shí)體,構(gòu)建的軟件系統(tǒng)就存在什么樣的實(shí)體印蔗。即便 Java 8 之后提供了對(duì)函數(shù)式編程的支持扒最,但是 Java 還是以面向?qū)ο鬄橹鞯恼Z(yǔ)言,函數(shù)式編程只是對(duì) Java 語(yǔ)言的補(bǔ)充华嘹。
Lambda 表達(dá)式概述
簡(jiǎn)單來(lái)說(shuō)吧趣,Lambda 表達(dá)式是創(chuàng)建匿名內(nèi)部類(lèi)的語(yǔ)法糖(syntax sugar)。在編譯器的幫助下耙厚,可以讓開(kāi)發(fā)人員用更少的代碼來(lái)完成工作强挫。
Lambda表達(dá)式標(biāo)準(zhǔn)語(yǔ)法形式如下:
(參數(shù)列表) -> {
//Lambda表達(dá)式體
}
舉例
// Lambda表達(dá)式實(shí)現(xiàn)Calculable接口
result = (int a, int b) -> {
return a + b;
};
函數(shù)式接口(Functional Interface)
Lambda 表達(dá)式實(shí)現(xiàn)的接口不是普通的接口,稱為是函數(shù)式接口薛躬,這種接口只能有一個(gè)方法俯渤。
在講Lambda表達(dá)式的時(shí)候提到過(guò),所謂的函數(shù)式接口型宝,當(dāng)然首先是一個(gè)接口八匠,然后就是在這個(gè)接口里面只能有一個(gè)抽象方法。
這種類(lèi)型的接口也稱為SAM接口趴酣,即 Single Abstract Method interfaces梨树。
為了防止在函數(shù)式接口中聲明多個(gè)抽象方法,Java 8提供了一個(gè)聲明函數(shù)式接口注解 @FunctionalInterface岖寞,用于編譯級(jí)錯(cuò)誤檢查抡四。這樣如果接口中聲明多個(gè)抽象方法,那么Lambda表達(dá)式會(huì)發(fā)生編譯錯(cuò)誤:The target type of this expression must be a functional interface仗谆。
// Calculable.java文件
@FunctionalInterface
public interface Calculable {
// 計(jì)算兩個(gè)int數(shù)值
int calculateInt(int a, int b);
}
注意:加不加@FunctionalInterface對(duì)于接口是不是函數(shù)式接口沒(méi)有影響指巡,該注解知識(shí)提醒編譯器去檢查該接口是否僅包含一個(gè)抽象方法
函數(shù)式接口用途
它們主要用在 Lambda 表達(dá)式和方法引用(實(shí)際上也可認(rèn)為是Lambda表達(dá)式)上。
那么就可以使用Lambda表達(dá)式來(lái)表示該接口的一個(gè)實(shí)現(xiàn)(注:JAVA 8 之前一般是用匿名類(lèi)實(shí)現(xiàn)的)胸私。
函數(shù)式接口里是可以包含默認(rèn)方法
因?yàn)槟J(rèn)方法不是抽象方法厌处,其有一個(gè)默認(rèn)實(shí)現(xiàn),所以是符合函數(shù)式接口的定義的岁疼;
函數(shù)式接口里允許定義 java.lang.Object 里的 public 方法
函數(shù)式接口里是可以包含 Object 里的 public 方法阔涉,這些方法對(duì)于函數(shù)式接口來(lái)說(shuō),不被當(dāng)成是抽象方法(雖然它們是抽象方法)捷绒;因?yàn)槿魏我粋€(gè)函數(shù)式接口的實(shí)現(xiàn)瑰排,默認(rèn)都繼承了 Object 類(lèi),包含了來(lái)自 java.lang.Object 里對(duì)這些抽象方法的實(shí)現(xiàn)暖侨。
JDK中的函數(shù)式接口舉例
- java.lang.Runnable,
- java.awt.event.ActionListener,
- java.util.Comparator,
- java.util.concurrent.Callable
- java.util.function包下的接口椭住,如Consumer、Predicate字逗、Supplier等京郑。
Lambda表達(dá)式簡(jiǎn)化形式
使用Lambda表達(dá)式是為了簡(jiǎn)化程序代碼宅广,Lambda 表達(dá)式本身也提供了多種簡(jiǎn)化形式,這些簡(jiǎn)化形式雖然簡(jiǎn)化了代碼些举,但客觀上使得代碼可讀性變差跟狱。本節(jié)介紹Lambda表達(dá)式本幾種簡(jiǎn)化形式。
- 省略參數(shù)類(lèi)型, Lambda表達(dá)式可以根據(jù)上下文環(huán)境推斷出參數(shù)類(lèi)型
- 省略參數(shù)小括號(hào), Lambda表達(dá)式中參數(shù)只有一個(gè)時(shí)可以省略
- 如果Lambda表達(dá)式體中只有一條語(yǔ)句户魏,那么可以省略return和大括號(hào)
例如result = a -> a * a; //省略形式
Lambda 表達(dá)式作為參數(shù)使用
Lambda 表達(dá)式一種常見(jiàn)的用途是作為參數(shù)傳遞給方法驶臊。這需要聲明參數(shù)的類(lèi)型為函數(shù)式接口類(lèi)型。
示例代碼如下:
public class HelloWorld {
public static void main(String[] args) {
int n1 = 10;
int n2 = 5;
// 打印計(jì)算結(jié)果減法計(jì)算結(jié)果
display((a, b) -> a - b, n1, n2);
}
/**
* 打印計(jì)算結(jié)果
*
* @param calc Lambda表達(dá)式
* @param n1 操作數(shù)1
* @param n2 操作數(shù)2
*/
static void display(Calculable calc, int n1, int n2) {
System.out.println(calc.calculateInt(n1, n2));
}
}
Lambda 作用域
在lambda表達(dá)式中訪問(wèn)外層作用域和老版本的匿名對(duì)象中的方式很相似叼丑。你可以直接訪問(wèn)標(biāo)記了final的外層局部變量关翎,或者實(shí)例的字段以及靜態(tài)變量。
方法引用
Java 8之后增加了雙冒號(hào)“::”運(yùn)算符鸠信,該運(yùn)算符用于“方法引用”纵寝,注意不是調(diào)用方法⌒橇ⅲ“方法引用”雖然沒(méi)有直接使用 Lambda 表達(dá)式店雅,但也與 Lambda 表達(dá)式有關(guān),與函數(shù)式接口有關(guān)贞铣。
方法引用的具體使用
java8方法引用有四種形式:
- 靜態(tài)方法引用 : ClassName :: staticMethodName
- 構(gòu)造器引用 【诿鳌: ClassName :: new
- 類(lèi)的任意對(duì)象的實(shí)例方法引用: ClassName :: instanceMethodName
- 特定對(duì)象的實(shí)例方法引用 ≡印: object :: instanceMethodName
注意 被引用方法的參數(shù)列表和返回值類(lèi)型,必須與函數(shù)式接口方法參數(shù)列表和方法返回值類(lèi)型一致荐健。
靜態(tài)方法引用 / 特定對(duì)象的實(shí)例方法適用于lambda表達(dá)式主體中僅僅調(diào)用了某個(gè)類(lèi)的靜態(tài)方法 / 對(duì)象的實(shí)例方法的情形酱畅。
構(gòu)造器引用適用于lambda表達(dá)式主體中僅僅調(diào)用了某個(gè)類(lèi)的構(gòu)造函數(shù)返回實(shí)例的場(chǎng)景。
類(lèi)的任意對(duì)象的實(shí)例方法引用的特性中江场,第一個(gè)入?yún)閷?shí)例方法的調(diào)用者纺酸,后面的入?yún)⑴c實(shí)例方法的入?yún)⒁恢隆?/p>
類(lèi)的任意對(duì)象的實(shí)例方法引用舉例:
// Supplier 要求void -> 出參。恰和對(duì)象.(VOID)->出參 匹配
String string = "12345";
Supplier<Integer> supplier = string::length;
System.out.println(supplier.get());
構(gòu)造器引用舉例:
// Supplier:void -> 出參:類(lèi)的;
Supplier<String> supplier = String::new;
System.out.println(supplier.get());
靜態(tài)方法引用 / 特定對(duì)象的實(shí)例方法舉例:
public class LambdaDemo {
// 靜態(tài)方法址否,進(jìn)行加法運(yùn)算
// 參數(shù)列表要與函數(shù)式接口方法calculateInt(int a, int b)兼容
public static int add(int a, int b) {
return a + b;
}
// 實(shí)例方法餐蔬,進(jìn)行減法運(yùn)算
// 參數(shù)列表要與函數(shù)式接口方法calculateInt(int a, int b)兼容
public int sub(int a, int b) {
return a - b;
}
}
public class HelloWorld {
public static void main(String[] args) {
int n1 = 10;
int n2 = 5;
// 打印計(jì)算結(jié)果加法計(jì)算結(jié)果
display(LambdaDemo::add, n1, n2);
LambdaDemo d = new LambdaDemo();
// 打印計(jì)算結(jié)果減法計(jì)算結(jié)果
display(d::sub, n1, n2);
}
/**
* 打印計(jì)算結(jié)果
* @param calc Lambda表達(dá)式
* @param n1 操作數(shù)1
* @param n2 操作數(shù)2
*/
public static void display(Calculable calc, int n1, int n2) {
System.out.println(calc.calculateInt(n1, n2));
}
}
靜態(tài)的 display 方法,第一個(gè)參數(shù) calc 是 Calculable類(lèi)型佑附,它可以接受三種對(duì)象:Calculable實(shí)現(xiàn)對(duì)象樊诺、Lambda表達(dá)式 或 方法引用。
代碼第 ① 行中第一個(gè)實(shí)際參數(shù)LambdaDemo::add 是靜態(tài)方法的方法引用音同。
代碼第 ② 行中第一個(gè)實(shí)際參數(shù)d::sub,是實(shí)例方法的方法引用词爬,d是LambdaDemo實(shí)例。
提示: 代碼第①行的LambdaDemo::add和第②行的d::sub中Lambda是方法引用权均,此時(shí)并沒(méi)有調(diào)用方法顿膨,只是將引用傳遞給display方法锅锨,在display方法中才真正調(diào)用方法。
參考
第 18 章 Java 8函數(shù)式編程基礎(chǔ)——Lambda表達(dá)式-圖靈社區(qū)
http://www.ituring.com.cn/book/tupubarticle/17714
Java 8 的 Lambda 表達(dá)式和流處理 – IBM Developer
https://developer.ibm.com/zh/articles/j-understanding-functional-programming-3/
Java 8 十大新特性詳解java腳本之家
https://www.jb51.net/article/48304.htm