如何使用Java 8 Lambda 基礎概念

如何使用Java 8 Lambda 基礎概念

在 Java 8 Lambda 表示式中,很容易讓原本寫習慣 Java 匿名函式的開發者搞混,如下面程式碼所示,建立一個簡單的 Runnable。

Runnable runnable = new Runnable(){
    public void run(){
        System.out.print("hello");
    }
};
new Thread(runnable);

這樣代表我們把一段任務塞入到 Thread,透過 Thread 去執行這一段 Task,一般來說很常見這樣的寫法, 普遍也很容易接受,但是改成Lambda表示式就會變成這樣。

Runnable runnable = () -> System.out.print("hello");
new Thread(runnable);

這樣還稍微可以接受,但是應該可以更精簡。

new Thread(()->System.out.print("hello"));

這樣就是一個標準的 Lambda 表示式,由於我們在宣告 Thread 這個類別的時候,其實已經將型態宣告出來了。

public class Thread implements Runnable {
    public Thread(Runnable target){
        //...
    }
    //...
}

因此再傳入的時候,不需要再宣告一次,編譯器自動會幫我判別,而 Runnable 只是一個 interface。

public interface Runnable {
    public abstract void run();
}

使用Lambda表示先決條件,interface 必須只有一個 method,才能夠使用,Lambda 表示會變成下面的宣告。

唯一的方法傳入的參數 -> 回傳值

所以 Runnable 會變成下面的樣子。

()->System.out.print("hello");

你會發現沒有傳入的參數就會變成 () 左右括弧來表示,而由於沒有回傳值, 因此我們用標準輸出一行呈現,這時候你就會問,那如果有多行輸出怎麼辦?
其實也不難,只要用左右大括弧{ }包起來就可以了。

()->{
    System.out.print("hello");
    System.out.print("World");
}

那如果有參數怎麼寫呢?
在Android按鈕要給定事件,通常都會這樣寫。

button.setOnClickListener(new OnClickListener(){
    public void onClick(Event e){
        Toast.makeText(context, "hello", Toast.LENGTH_SHORT).show();
    }
});

轉換成我們剛剛所推導的流程,這個事件可以變成這樣。

button.setOnClickListener(e->
    Toast.makeText(context, "hello", Toast.LENGTH_SHORT).show());

如果是多行就會變成下面程式碼所示。

button.setOnClickListener(e-> {
    Toast.makeText(context, "hello", Toast.LENGTH_SHORT).show();
    Toast.makeText(context, "world", Toast.LENGTH_SHORT).show();
});

此時你會發現單一行呈現,不需要用;分號做結尾,但是,多行則必須每一行都以;做結尾。
那如果是有回傳參數的方法呢?
我們利用 Callable 來進行解說,Callable 跟 Runnable 一樣都是一個interface,唯一不同的是它有一個泛型的回傳值。

public interface Callable<V> {
    V call() throws Exception;
}

所以再傳入的時候,我們必須指定泛型的型態,從以下例子來看,透過一個 ThreadPool 傳入一個 Task,並且透過 Callable 回傳結果。

Executors.newSingleThreadExecutor().submit(new Callable<String>(){
    @Override
    public String call() throws Exception {
        String s = "hello";
        return s;
    }
});

這樣就可以很清楚的看到,當任務執行完畢,會將hello這個字串回傳回去,那如果換成 Lambda 就可以這樣寫。

Executors.newSingleThreadExecutor().submit(()->"hello");

當只有一行敘述的時候,該行敘述會被視為回傳值,因此,如果你直接寫成下面的程式碼。

Executors.newSingleThreadExecutor().submit(()->1);

編譯器會判定你的T型態為整數,也就是說會變成以下的類型。

public interface Callable<Integer> {
    Integer call() throws Exception;
}

假設你是以標準輸出呈現,如下面程式碼所示。

Executors.newSingleThreadExecutor().submit(()->System.out.print("hello"));

則編譯器會認為你 Callable<Void> 以 Void 的型態呈現,因此,泛型的好處是各種彈性回傳都會讓編譯器通過編譯,那假設有一個interface 是固定型態呢?
舉個例子來說,我們假定有一個介面用來檢查是否合身?
如下面程式碼所示。

private class Wear{
    void wearClothes(Fit fit) {

    }
}
public interface Fit{
    boolean onFit();
}

那麼我們實作這個介面就可以照下面程式碼這樣寫。

new Wear().wearClothes(new Fit() {
    @Override
    public boolean onFit() {
        return false;
    }
});

轉換成 Lambda 表示式就會變成下面程式碼這樣。

new Wear().wearClothes(()->false);

此時,如果你將回傳值改成數值,就會變成下面程式碼這樣。

new Wear().wearClothes(()->1);

那麼編譯器就不會讓你過了,這樣就可以清楚看出泛型跟非泛型的差異了,泛型彈性也比較足夠,好處是同樣一個 interface 可以讓多種形態適用。
回到剛剛的例子,我們一值都是傳入 0 個參數或者 1 個參數,那如果是兩個參數怎麼辦?
我們把上面的例子改成以下狀況。

private class Wear{
    void wearClothes(Fit fit) {
    }
}
public interface Fit{
    boolean onFit(int height, int waist);
}

覆寫完的結果就會變成下面的狀況。

new Wear().wearClothes(new Fit() {
    @Override
    public boolean onFit(int height, int waist) {
        return false;
    }
});

跟剛剛好像其實只是多了一個參數,那我們來看看 Lambda 怎麼表示?

new Wear().wearClothes((h, w) ->false);

變得非常的簡短,而且 h 跟 w 並不知道是甚麼樣的型態,這樣有可能會造成隊友短時間的眼睛痛,所以你可以補上一些資訊。

new Wear().wearClothes((int h, int w) ->false);

這邊要注意的是,如果你一個參數有補上型態,另外一個參數也要跟著補上,一種要補大家一起補的概念。

new Wear().wearClothes((int h, w) ->false);

這樣編譯器不會讓你過。
那一個參數可以補足型態嗎?當然可以。
假設 interface 改成下面這樣子。

private static class Wear{
    void wearClothes(Fit fit) {

    }
}
public interface Fit{
    boolean onFit(int height);
}

Lambda 就可以表示成下面程式碼這樣。

new Wear().wearClothes((int h) ->false);

一定要左右括弧(``)嗎? 沒錯! 一定要。
否則編譯器仍然不會讓你過。
大致上在 Lambda 語法簡化處理的部分都講完了,Java 8 提供了一些函式讓 Lambda 在操作上更容易辨認,只需要認定某些方法,就可以清楚知道傳入的參數跟回傳的參數是那些,以下列出常見的寫法。

函式介面 回傳類型 方法名稱 傳入參數
Predicate<T> boolean test T
Supplier<T> T get void
Comsumer<T> void accept T
Function <T, R> R apply T

這樣就是一個簡單的 Lambda 範例了。