Java8學習系列之匿名函式Lambda

矢澤妮可發表於2019-01-24

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的強大之處。

參考文章

相關文章