Lambda介紹
Lambda,別名函數語言程式設計,維基百科給出以下介紹:
函數語言程式設計是一種程式設計正規化。它把計算當成是數學函式的求值,從而避免改變狀態和使用可變資料。它是一種宣告式的程式設計正規化,通過表示式和宣告而不是語句來程式設計。
Lambda表示式基於數學中的λ演算得名,直接對應於其中的lambda抽象(lambda abstraction),是一個匿名函式,即沒有函式名的函式。Lambda表示式可以表示閉包(注意和數學傳統意義上的不同)。
λ 演算是數理邏輯中的一個形式系統,在函式抽象和應用的基礎上,使用變數繫結和替換來表達計算。討論 λ 演算離不開形式化的表達。在本文中,我們儘量集中在與程式設計相關的基本概念上,而不拘泥於數學上的形式化表示。λ 演算實際上是對前面提到的函式概念的簡化,方便以系統的方式來研究函式。
Java中的Lambda
自Java8面世以後,也就代表著java從此以後同樣支援lambda語法,使得之前繁瑣的操作都可以使用簡便的語法進行代替,最具代表性的改革就是新增的Stream類,讓我們對一個集合的排序、過濾、對映和採集更加方便!
我們擬定一個場景,對於給定的一個int陣列,過濾掉負數,並對剩餘的元素進行排序,在java8之前我們的實現需要這麼寫:
int[] array = {7, -2, 3, 5, -9, 3, -5, -1, 6, 8, 20};
List<Integer> list = new ArrayList<Integer>();
//過濾負數
for(int i: array) {
if(i >= 0) list.add(i);
}
//排序
Collections.sort(list);
for(int i: list) {
System.out.println(i);
}
複製程式碼
使用Stream之後:
int[] array = {7, -2, 3, 5, -9, 3, -5, -1, 6, 8, 20};
Arrays.stream(array)
.filter(a -> a >= 0) //過濾
.sorted() //排序
.forEach(System.out::println);
複製程式碼
可以看到,實現的過程更加簡潔和優雅,lambda大大節省了程式碼空間,提升了程式碼可讀性,但使用的難度也隨之提高,對於傳統的程式設計方式,lambda語法無疑是一次重大的衝擊。
Java中Lambda語法的使用
函式式介面
什麼是函式式介面呢?在Java8之前,我們想實現一個介面,最簡單的方式直接使用匿名類:
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 > o2 ? 1 : -1;
}
};
複製程式碼
這裡要注意,Comparator是一個介面型別,它的內部只有一個需要被實現的方法,那麼我們將之稱之為函式式介面,一般的函式式介面都會加上@FunctionalInterface
註解,如果該介面待實現的方法超出兩個,你的IDE就會提醒你這不是一個規範的函式式介面,對於符合的,我們就可以使用lambda語法進行初始化:
Comparator<Integer> comparator = (o1, o2) -> o1 > o2 ? 1 : -1;
複製程式碼
將之與java8之前的實現對比,我們發現有很多共同之處,我們來分析一下lambda的實現:
(o1, o2) -> o1 > o2 ? 1 : -1;
複製程式碼
將上部分以->
做分割線,分成兩部分,它們分別是(o1, o2)
和o1 > o2 ? 1 : -1
。很明顯,前者代表著函式的兩個入參,後者代表著兩個入參的邏輯實現,由此可得,lambda由兩部分組成:入參定義
和邏輯實現
。
對於一個函式式介面,我們可以用簡單的lambda語法去實現介面內唯一的待實現方法,反推一下,對於lambda這種匿名的函式定義風格,如果一個介面存在兩個待實現的方法,lambda則無法具體表示實現的是哪一個方法,由此反推可得,一個函式式介面最多隻能有一個待實現方法。
JDK對Lambda的支援
通過函式式介面的定義和lambda實現我們知道了lambda語法的一個簡單格式,但是在開發過程中,我們不可能對於每一個lambda的應用都定義個函式式介面,實際上,JDK中已經存在了很多lambda函式:
- Function<T, R>:接受一個引數輸入,輸入型別為 T,輸出型別為 R。 抽象方法為
R apply(T)
。 - BiFunction<T, U, R>:接受兩個引數輸入, T 和 U 分別是兩個引數的型別,R 是輸出型別。抽象方法為
R apply(T, U)
。 - Consumer:接受一個輸入,沒有輸出。抽象方法為
void accept(T t)
。 - Predicate:接受一個輸入,輸出為 boolean 型別。抽象方法為
boolean test(T t)
。 - Supplier:沒有輸入,一個輸出。抽象方法為
T get()
。 - BinaryOperator:接受兩個型別相同的輸入,輸出的型別與輸入相同,相當於 BiFunction<T,T,T>。
- UnaryOperator:接受一個輸入,輸出的型別與輸入相同,相當於 Function<T, T>。
- BiPredicate<T, U>:接受兩個輸入,輸出為 boolean 型別。抽象方法為 boolean test(T t, U u)。
它們分別應用於不同的場景,以下將會有幾個演示,首先使用lambda實現一個計算器:
BinaryOperator<Integer> cal = (a, b) -> a + b;
System.out.println(bo.apply(1, 2)); // 3
複製程式碼
再來一個,使用lambda實現對數字正負的判斷
int a = 1;
int b = -1;
Predicate<Integer> predicate = i -> i >= 0;
System.out.println(predicate.test(a)); //true
System.out.println(predicate.test(b)); //false
複製程式碼
總結
在Stream中,lambda的應用非常廣泛,我們如果想講lambda更熟練的掌握,需要自己親自的去使用lambda,在實戰中去真正體會lambda的強大之處。