程式碼示例:
java.lambda.LambdaExpression
1 本質
lambda
表示式本質上是對匿名內部類例項的一種簡化寫法。
1.1 案例
有以下List<Integer>
物件:
List<Integer> list = Arrays.asList(1, 3, 5, 7, 9, 2, 4, 6, 8, 10);
在對List
進行從小大大排序時,會用到List#sort(Comparator)
方法,需要傳遞實現Comparator
介面的物件作為引數:
default void sort(Comparator<? super E> c) {
// 省略方法體
}
可以想到有如下四種不同的程式碼編寫的方式。
1、 建立Comparator
的實現類
根據需求,手動實現Comparator
介面:
public class AscComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
}
然後,建立AscComparator
例項,傳給List#sort(Comparator)
方法:
Comparator<Integer> ascComparator = new AscComparator();
list.sort(ascComparator);
2、建立Comparator
的匿名物件
可以直接建立Comparator
的匿名物件,然後傳給List#sort(Comparator)
方法:
Comparator<Integer> anonymousComparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
list.sort(anonymousComparator);
等價於:
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
3、lambda
表示式
直接使用lambda
表示式:
list.sort((o1, o2) -> o1.compareTo(o2));
4、方法引用
使用方法引用(方法引用具體概念和使用可以檢視相關文章):
list.sort(Integer::compare);
可以明顯看出,使用lambda
表示式和方法引用極大提高了開發的速度,提升了程式碼的簡潔性。
1.2 本質
實際上,lambda
表示式只是JVM提供的語法糖。在JVM執行過程中,會根據lambda
表示式的規則,動態建立出匿名的介面實現類物件。
lambda
表示式本質上是Java物件。
可以透過檢視lambda
表示式的Class
物件和例項物件來證明這一點:
public class LambdaExpression {
public void printConsumer(Consumer consumer) {
System.out.println(consumer.getClass());
System.out.println(consumer.getClass().getInterfaces()[0]);
System.out.println(consumer);
}
}
1、案例1
執行以下程式碼:
LambdaExpression lambdaObjPrinter = new LambdaExpression();
lambdaObjPrinter.printConsumer(o -> o.getClass());
lambdaObjPrinter.printConsumer(o -> o.getClass());
會有如下輸出:
class lambda.LambdaExpression$$Lambda$1/2003749087
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$1/2003749087@41629346
class lambda.LambdaExpression$$Lambda$2/1078694789
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$2/1078694789@6d311334
- 這證明了執行過程中會根據
lambda
表示式動態生成函式式介面的實現類,並建立該實現類的例項。 - 同時,先後執行的2個
lambda
表示式,儘管格式相同,仍然動態生成了2個實現類。
檢視編譯後的.class
檔案如下:
LambdaExpression lambdaObjPrinter = new LambdaExpression();
lambdaObjPrinter.printConsumer((o) -> {
o.getClass();
});
lambdaObjPrinter.printConsumer((o) -> {
o.getClass();
});
2、案例2
執行如下程式碼:
LambdaExpression lambdaObjPrinter = new LambdaExpression();
for (int i = 0; i < 2; i++) {
lambdaObjPrinter.printConsumer(o -> o.getClass());
}
System.out.println("=============");
for (int i = 0; i < 2; i++) {
lambdaObjPrinter.printConsumer(o -> o.getClass());
}
會發現有如下輸出:
class lambda.LambdaExpression$$Lambda$1/2003749087
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$1/2003749087@41629346
class lambda.LambdaExpression$$Lambda$1/2003749087
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$1/2003749087@41629346
=============
class lambda.LambdaExpression$$Lambda$2/1078694789
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$2/1078694789@6d311334
class lambda.LambdaExpression$$Lambda$2/1078694789
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$2/1078694789@6d311334
- 說明在不同
for
迴圈中(while
等迴圈結果相同),只會動態生成1個實現類。
檢視編譯後的.class
檔案如下:
LambdaExpression lambdaObjPrinter = new LambdaExpression();
int i;
for(i = 0; i < 2; ++i) {
lambdaObjPrinter.printConsumer((o) -> {
o.getClass();
});
}
System.out.println("=============");
for(i = 0; i < 2; ++i) {
lambdaObjPrinter.printConsumer((o) -> {
o.getClass();
});
}
- 說明這不是編譯器編譯的結果,應該是JVM執行時對迴圈語句中
lambda
表示式的最佳化。
2 基本語法
lambda
表示式本質上是對函式式介面的匿名實現類例項的一種簡化寫法:方法格式和lambda
表示式格式一一對應。
對於執行邏輯而言,方法主要由兩部分組成(沒有返回值):形參和方法體。
lambda
表示式與之對應:
1、形參:(t1, t2[, ……])
,對應方法的形參(T1 t1, T2 t2[, ……])
2、箭頭:->,固定
3、方法體:{},對應方法的方法體
2.1 分類
根據方法形參和返回值的不同組合,lambda表示式可以分成以下幾類:
- 沒有形參:
() -> {
// 方法體
}
- 一個形參:
(t) -> {
// 方法體
}
-
多個形參:
(t1, t2[, ……]) -> {
// 方法體
} -
沒有返回值:
() -> {
// 方法體
}
- 有返回值:
() -> {
// 方法體
return something;
}
根據形參個數的不同,形參部分可以有不同的寫法:
1、沒有形參或多個形參(超過1個),需要帶()
:
() -> {
// 方法體
}
(t1, t2[, ……]) {
// 方法體
}
2、一個形參,可以省略()
:
(t) -> {
// 方法體
}
t -> {
// 方法體
}
根據方法體中程式碼行數的不同,方法體部分也有不同的寫法:
1、一行程式碼,可以省略{}
(此時該行程式碼的return
和;
也必須省略):
() -> {
System.out.println("Hello World!");
}
() -> System.out.println("Hello World!")
() -> {
return "Hello World!"
}
() -> "Hello World!"
2、多行程式碼,不可以省略{}
:
() -> {
System.out.println("Hello");
System.out.println("World!");
}
() -> {
System.out.println("Hello");
return "Hello World!"
}
2.2 案例
- 定義函式式介面,模擬不同型別的
lambda
表示式:
public class FunctionInterface {
interface AcceptEmpty {
void accept();
}
interface AcceptOne<T> {
void accept(T t);
}
interface AcceptMore<T, E> {
void accept(T t, E e);
}
interface ReturnVoid {
void returnVoid();
}
interface ReturnR<R> {
R returnR();
}
}
- 定義呼叫類,接收不同的
lambda
表示式:
/**
* 呼叫函式式介面的服務類
* @param <T> 第一個形參型別
* @param <E> 第二個形參型別
* @param <R> 返回值型別
*/
public class Service<T, E, R> {
private T t;
private E e;
public Service(T t, E e) {
this.t = t;
this.e = e;
}
void acceptEmpty(FunctionInterface.AcceptEmpty acceptEmpty) {
acceptEmpty.accept();
}
void acceptOne(FunctionInterface.AcceptOne<T> acceptOne) {
acceptOne.accept(this.t);
}
void acceptMore(FunctionInterface.AcceptMore<T, E> acceptMore) {
acceptMore.accept(this.t, this.e);
}
void returnVoid(FunctionInterface.ReturnVoid returnVoid) {
returnVoid.returnVoid();
}
R returnR(FunctionInterface.ReturnR<R> returnR) {
return returnR.returnR();
}
}
- 建立服務類例項:
Service<Integer, Integer, String> service = new Service<>(1, 2);
1、沒有形參
service.acceptEmpty(new FunctionInterface.AcceptEmpty() {
@Override
public void accept() {
System.out.println("沒有形參");
}
});
service.acceptEmpty(() -> {
System.out.println("沒有形參");
});
service.acceptEmpty(() -> System.out.println("沒有形參"));
2、一個形參
service.acceptOne(new FunctionInterface.AcceptOne<Integer>() {
@Override
public void accept(Integer t) {
System.out.println(t);
}
});
service.acceptOne((t) -> System.out.println(t));
service.acceptOne(t -> System.out.println(t));
3、多個形參
service.acceptMore(new FunctionInterface.AcceptMore<Integer, Integer>() {
@Override
public void accept(Integer t, Integer e) {
System.out.println(t);
System.out.println(e);
}
});
service.acceptMore((t, e) -> {
System.out.println(t);
System.out.println(e);
});
4、沒有返回值
service.returnVoid(new FunctionInterface.ReturnVoid() {
@Override
public void returnVoid() {
System.out.println("沒有返回值");
}
});
service.returnVoid(() -> System.out.println("沒有返回值"));
5、有返回值
service.returnR(new FunctionInterface.ReturnR<String>() {
@Override
public String returnR() {
return "3";
}
});
service.returnR(() -> "3");
3 執行邏輯
lambda
表示式本質上是傳遞了一個動態生成的匿名物件,是一種假的函數語言程式設計。
lambda
表示式形式上看起來很像是函數語言程式設計:將一個函式當作形參傳給方法。
實際上,lambda表示式只是Java的一個語法糖,它本質上仍然是一個普通的Java物件。
在執行的過程中,lambda表示式最終還是會被解析成匿名的介面實現類物件。
由於多型特性,在執行過程中,呼叫是外部傳進來的實現類例項的程式碼。
在這個過程中,我們甚至可以將該匿名物件儲存起來,便於後續多次呼叫。
- 定義一個函式式介面:
public interface Lambda<T, R> {
R method(T t);
}
- 定義呼叫類:
public class FakeFunctionalProgramming<T, R> {
private T t;
private Lambda<T, R> lambda;
public void setT(T t) {
this.t = t;
}
public void setLambda(Lambda<T, R> lambda) {
this.lambda = lambda;
}
public void doSomeThing() {
T t = before();
R r = lambda.method(t);
after(r);
}
public T before() {
return t;
}
public void after(R r) {
System.out.println(r);
}
}
- 執行以下程式碼:
FakeFunctionalProgramming<String, String> ffp = new FakeFunctionalProgramming<>();
ffp.setT("Xianhuii");
ffp.setLambda((t) -> "Hello " + t + "!");
ffp.doSomeThing(); // Hello Xianhuii!
從上述結果可以看出,lambda
表示式的程式設計方式本質上是利用了多型的特性,同時又使用了模板方法模式:
- 呼叫處接收一個介面例項
Lambda<T, R>
作為形參。 - 執行
before()
方法,處理相對固定的前處理邏輯。 - 將執行過程中相關值作為形參傳給
Lambda<T, R>
例項,進行特定處理。 - 接收
Lambda<T, R>
特定處理後的返回值。 - 執行
after()
方法,處理相對固定的後處理邏輯。
此時,我們應該能夠透徹理解lambda
表示式中形參的來源,返回值的去向了。
藉助Java多型特性,以及JVM動態生成匿名實現類例項的功能,lambda
表示式才表現得那麼像是函數語言程式設計。