Java 8 之后推出的 Lambda 表達式開啟了 Java 語言支持函數(shù)式編程(Functional Programming)的新時代。
Lambda 表達式,也稱為閉包(Closure),現(xiàn)在很多語言都支持 Lambda表達式,如 C++、C#、Swift、Objective-C 和 JavaScript 等。為什么 Lambda 表達式這怎么受歡迎,這是因為Lambda表達式是實現(xiàn)支持函數(shù)式編程技術基礎。
函數(shù)式編程與面向對象編程有很大的差別,函數(shù)式編程將程序代碼看作數(shù)學中的函數(shù),函數(shù)本身作為另一個函數(shù)的參數(shù)或返回值,即高階函數(shù)。而面向對象編程是按照真實世界客觀事物的自然規(guī)律進行分析,客觀世界中存在什么樣的實體,構建的軟件系統(tǒng)就存在什么樣的實體。即便 Java 8 之后提供了對函數(shù)式編程的支持,但是 Java 還是以面向對象為主的語言,函數(shù)式編程只是對 Java 語言的補充。
Lambda 表達式概述
簡單來說,Lambda 表達式是創(chuàng)建匿名內部類的語法糖(syntax sugar)。在編譯器的幫助下,可以讓開發(fā)人員用更少的代碼來完成工作。
Lambda表達式標準語法形式如下:
(參數(shù)列表) -> {
//Lambda表達式體
}
舉例
// Lambda表達式實現(xiàn)Calculable接口
result = (int a, int b) -> {
return a + b;
};
函數(shù)式接口(Functional Interface)
Lambda 表達式實現(xiàn)的接口不是普通的接口,稱為是函數(shù)式接口,這種接口只能有一個方法。
在講Lambda表達式的時候提到過,所謂的函數(shù)式接口,當然首先是一個接口,然后就是在這個接口里面只能有一個抽象方法。
這種類型的接口也稱為SAM接口,即 Single Abstract Method interfaces。
為了防止在函數(shù)式接口中聲明多個抽象方法,Java 8提供了一個聲明函數(shù)式接口注解 @FunctionalInterface,用于編譯級錯誤檢查。這樣如果接口中聲明多個抽象方法,那么Lambda表達式會發(fā)生編譯錯誤:The target type of this expression must be a functional interface。
// Calculable.java文件
@FunctionalInterface
public interface Calculable {
// 計算兩個int數(shù)值
int calculateInt(int a, int b);
}
注意:加不加@FunctionalInterface對于接口是不是函數(shù)式接口沒有影響,該注解知識提醒編譯器去檢查該接口是否僅包含一個抽象方法
函數(shù)式接口用途
它們主要用在 Lambda 表達式和方法引用(實際上也可認為是Lambda表達式)上。
那么就可以使用Lambda表達式來表示該接口的一個實現(xiàn)(注:JAVA 8 之前一般是用匿名類實現(xiàn)的)。
函數(shù)式接口里是可以包含默認方法
因為默認方法不是抽象方法,其有一個默認實現(xiàn),所以是符合函數(shù)式接口的定義的;
函數(shù)式接口里允許定義 java.lang.Object 里的 public 方法
函數(shù)式接口里是可以包含 Object 里的 public 方法,這些方法對于函數(shù)式接口來說,不被當成是抽象方法(雖然它們是抽象方法);因為任何一個函數(shù)式接口的實現(xiàn),默認都繼承了 Object 類,包含了來自 java.lang.Object 里對這些抽象方法的實現(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表達式簡化形式
使用Lambda表達式是為了簡化程序代碼,Lambda 表達式本身也提供了多種簡化形式,這些簡化形式雖然簡化了代碼,但客觀上使得代碼可讀性變差。本節(jié)介紹Lambda表達式本幾種簡化形式。
- 省略參數(shù)類型, Lambda表達式可以根據(jù)上下文環(huán)境推斷出參數(shù)類型
- 省略參數(shù)小括號, Lambda表達式中參數(shù)只有一個時可以省略
- 如果Lambda表達式體中只有一條語句,那么可以省略return和大括號
例如result = a -> a * a; //省略形式
Lambda 表達式作為參數(shù)使用
Lambda 表達式一種常見的用途是作為參數(shù)傳遞給方法。這需要聲明參數(shù)的類型為函數(shù)式接口類型。
示例代碼如下:
public class HelloWorld {
public static void main(String[] args) {
int n1 = 10;
int n2 = 5;
// 打印計算結果減法計算結果
display((a, b) -> a - b, n1, n2);
}
/**
* 打印計算結果
*
* @param calc Lambda表達式
* @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表達式中訪問外層作用域和老版本的匿名對象中的方式很相似。你可以直接訪問標記了final的外層局部變量,或者實例的字段以及靜態(tài)變量。
方法引用
Java 8之后增加了雙冒號“::”運算符,該運算符用于“方法引用”,注意不是調用方法?!胺椒ㄒ谩彪m然沒有直接使用 Lambda 表達式,但也與 Lambda 表達式有關,與函數(shù)式接口有關。
方法引用的具體使用
java8方法引用有四種形式:
- 靜態(tài)方法引用 ?。骸? ClassName :: staticMethodName
- 構造器引用 : ClassName :: new
- 類的任意對象的實例方法引用: ClassName :: instanceMethodName
- 特定對象的實例方法引用 ?。骸? object :: instanceMethodName
注意 被引用方法的參數(shù)列表和返回值類型,必須與函數(shù)式接口方法參數(shù)列表和方法返回值類型一致。
靜態(tài)方法引用 / 特定對象的實例方法適用于lambda表達式主體中僅僅調用了某個類的靜態(tài)方法 / 對象的實例方法的情形。
構造器引用適用于lambda表達式主體中僅僅調用了某個類的構造函數(shù)返回實例的場景。
類的任意對象的實例方法引用的特性中,第一個入?yún)閷嵗椒ǖ恼{用者,后面的入?yún)⑴c實例方法的入?yún)⒁恢隆?/p>
類的任意對象的實例方法引用舉例:
// Supplier 要求void -> 出參。恰和對象.(VOID)->出參 匹配
String string = "12345";
Supplier<Integer> supplier = string::length;
System.out.println(supplier.get());
構造器引用舉例:
// Supplier:void -> 出參:類的;
Supplier<String> supplier = String::new;
System.out.println(supplier.get());
靜態(tài)方法引用 / 特定對象的實例方法舉例:
public class LambdaDemo {
// 靜態(tài)方法,進行加法運算
// 參數(shù)列表要與函數(shù)式接口方法calculateInt(int a, int b)兼容
public static int add(int a, int b) {
return a + b;
}
// 實例方法,進行減法運算
// 參數(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;
// 打印計算結果加法計算結果
display(LambdaDemo::add, n1, n2);
LambdaDemo d = new LambdaDemo();
// 打印計算結果減法計算結果
display(d::sub, n1, n2);
}
/**
* 打印計算結果
* @param calc Lambda表達式
* @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 方法,第一個參數(shù) calc 是 Calculable類型,它可以接受三種對象:Calculable實現(xiàn)對象、Lambda表達式 或 方法引用。
代碼第 ① 行中第一個實際參數(shù)LambdaDemo::add 是靜態(tài)方法的方法引用。
代碼第 ② 行中第一個實際參數(shù)d::sub,是實例方法的方法引用,d是LambdaDemo實例。
提示: 代碼第①行的LambdaDemo::add和第②行的d::sub中Lambda是方法引用,此時并沒有調用方法,只是將引用傳遞給display方法,在display方法中才真正調用方法。
參考
第 18 章 Java 8函數(shù)式編程基礎——Lambda表達式-圖靈社區(qū)
http://www.ituring.com.cn/book/tupubarticle/17714
Java 8 的 Lambda 表達式和流處理 – IBM Developer
https://developer.ibm.com/zh/articles/j-understanding-functional-programming-3/
Java 8 十大新特性詳解java腳本之家
https://www.jb51.net/article/48304.htm