2. 初探Lambda運算式

2.1 Lambda的由來

在尚未發明電腦的時代,有個邏輯學家Alonzo Church發明了使用希臘字母lambda(λ)來表示參數的方法。而在程式中,「lambda運算式」就被用來代表一段程式區塊,可以被當參數傳遞、可被重複執行。

以往在JAVA中,要達成類似傳遞程式區塊的方法,通常都要藉助實做介面或抽象類別,例如,使用執行緒來執行一個方法,就必須實作Runnable介面:


package exec02;

public class Worker implements Runnable {

    @Override
    public void run() { //這裡有我們想要執行的程式碼!
        for (int i = 0; i < 10; i++) {
            System.out.println("loop " + i);
        }
    }

    public static void main(String[] args) {
        Worker w = new Worker();
        new Thread(w).start();
    }
}

此處的關鍵在於:執行緒會呼叫Runnable介面的run方法,而run方法含有我們想要執行的程式碼!

再來看一個例子,藉由實作一個Comparator介面的類別,來排序一個陣列:


package exec02;

import java.util.Arrays;
import java.util.Comparator;

public class LengthComparator implements Comparator {

    @Override
    public int compare(String o1, String o2) {  //這裡有我們想要執行的程式碼!
        return Integer.compare(o1.length(), o2.length());
    }

    public static void main(String[] args) {
        String[] strings = new String[]{"Python", "Java", "Swift", "Objective-C", "PHP"};
        Arrays.sort(strings, new LengthComparator());
        for (String string : strings) {
            System.out.println(string);
        }
    }
}

因此以過往在JAVA中,要達成傳遞程式區塊的目的,就得先建立物件,並在物件的方法裡塞入想要的程式碼,並傳遞出去,只是這種方法,常常會造成程式碼過於冗長,並有過多的「樣版代碼」,程式碼一點都不簡潔且無法清楚表現其業務邏輯,根本可以用「囉嗦」來形容。

由此看來,JavaScript似乎方便多了,可以直接將函式作為變數傳遞!可惜的是「JavaScript ≠ Java」,就像「熱狗 ≠ 狗」,二個語言一開始設計的理念就不盡相同,加上JAVA有許多的歷史包袱,因此無法說改就改。

2.2 使用Lambda

前面的例子,我們還可以寫得更簡潔一點,方式就是使用匿名類別(通常是一次性使用):


package exec02;

import java.util.Arrays;
import java.util.Comparator;

public class AnonymousClass {

    public static void main(String[] args) {

        new Thread(new Runnable() { //Runnable匿名類別
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("loop " + i);
                }
            }
        }).start();

        String[] strings = new String[]{"Python", "Java", "Swift", "Objective-C", "PHP"};
        Arrays.sort(strings, new Comparator<String>() { //Comparator匿名類別
            @Override
            public int compare(String o1, String o2) {
                return Integer.compare(o1.length(), o2.length());
            }
        });
        for (String string : strings) {
            System.out.println(string);
        }
    }
}

程式碼似乎看起來簡潔多了,少了重新宣告一個類別的動作,但仍然要手動NEW出一個物件並實作抽象方法,這些其實都是跟業務邏輯無關的樣版代碼,對JAVA稍有經驗的人,誰不知道要跑執行緒就是要丟進一個Runnable物件、要排序就是要丟進一個Comparator物件呢?這些固定的樣版代碼看起來似乎是可以由電腦幫我們代勞自動加上去、甚至省略掉的嗎?(IDE的確是可以自動幫忙加上去,但無法省略)

經過千呼萬喚,JAVA 8終於出現了Lambda運算式,來達成程式碼簡潔的要求。

我們再將上述匿名類別的例子改寫為Lambda:


//改寫Runnable匿名類別
() -> {
    for (int i = 0; i < 10; i++) {
        System.out.println("loop " + i);
    }
}

//改寫Comparator匿名類別
(String o1, String o2) -> Integer.compare(o1.length(), o2.length())

因此更簡單的講,Lambda就是一種用「(參數1, 參數2...) -> 運算式 」的表達方式。

要注意的是,如果程式碼無法以一條運算式來表式,那麼就要用大括號{}包起來,有回傳值的話則要明確寫上return語句:


(String o1, String o2) -> {
    if(o1.length() < o2.length()) return -1;
    else if(o1.length() > o2.length()) return 1;
    else return 0;
}

要是沒有參數,仍要有空括號,就像是沒有參數的方法一樣:


() -> System.out.println("Hello World!")

藉由型態推導,JAVA可以知道參數及回傳的型別,因此有時是可以省略參數型別的:


(o1, o2) -> Integer.compare(o1.length(), o2.length())

若只有一個參數,且型別可由推導得知,那麼也可以省略掉括號:


name -> System.out.println("Hello " + name)

恭喜您,您已經學會Lambda運算式的使用方法了!