HOWTO · Java

Java 中的箭頭運算子 ->

這篇文章就是要了解 Java 中的箭頭運算子。

本頁內容

在 Java 中,-> 是 lambda 表示式的分隔符號,不是通用的計算運算子。它將 lambda 左側的參數與右側的主體分開。Lambda 會為相容的函式介面提供實作;函式介面是只有一個抽象方法的介面。Lambda 在 Java 8 中引入,能讓某些實作更精簡,但不能取代所有匿名類別,也不保證程式碼一定更快或更清楚。

Java 中箭頭運算子的意義

在 Java 8 之前,可以用包含 run 方法的匿名類別來撰寫 Runnable 工作。Lambda 可以用 () -> System.out.println("Working") 表達相同行為。箭頭仍屬於 lambda 表示式的一部分;它不會建立不依賴目標型別的獨立函式值。編譯器會根據變數宣告或方法引數等周遭情境,判斷目標函式介面的型別。

Lambda 表示式的語法與規則

Lambda 由參數清單、箭頭和主體組成。表達式主體可以省略大括號,例如 x -> x * x;區塊主體則使用大括號,而且當目標方法有回傳值時,必須明確寫出 return。沒有參數的 lambda 使用 ();多個參數則以括號括起來,例如 (left, right) -> left + right。單一參數也可以加上括號,例如 (name) -> name.length()。

若區塊主體的 lambda 會回傳值,目標方法的回傳型別會決定 lambda 的結果型別。以下範例將 lambda 指派給 ToIntFunction<String>,其 applyAsInt 方法會回傳 int:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

import java.util.function.ToIntFunction;

public class BlockLambdaExample {
    public static void main(String[] args) {
        ToIntFunction<String> length = (String name) -> {
            return name.length();
        };
        System.out.println(length.applyAsInt("Mira"));
    }
}

主體必須符合目標方法的回傳型別與受檢例外規則。Lambda 不能指派給任意類別,也不能指派給具有多個彼此無關之抽象方法的介面。依照這項規則,介面的預設方法與靜態方法不會算作額外的抽象方法。

使用 Lambda 搭配函式介面

Lambda 可以指派給簡單的函式介面,再透過該介面的方法呼叫。這個獨立範例先列出包含 main 的類別,因此可以直接執行。其輸出是 draw() 印出的那一行。

執行此範例:程式碼與輸出中的文字標籤維持英文,與不可變更的範例內容一致。

本節中的程式碼以及顯示的標籤或輸出仍為英文。

public class ArrowExample {
    @FunctionalInterface
    interface Drawable {
        void draw();
    }

    public static void main(String[] args) {
        Drawable shape = () -> System.out.println("Drawing a circle");
        shape.draw();
    }
}
Drawing a circle

Java 集合、串流與執行緒中的常見用途

forEach 等集合方法會接受函式介面,因此可以用 lambda 描述要對每個元素執行的動作。在此範例中,name 代表清單目前的元素,而 Consumer<String> lambda 會將它印出。範例中的名稱維持英文。

本節中的程式碼以及顯示的標籤或輸出仍為英文。

import java.util.Arrays;
import java.util.List;

public class CollectionLambdaExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Ava", "Mira");
        names.forEach(name -> System.out.println(name));
    }
}

串流的 filter 方法會接收 Predicate<String> lambda,以保留符合條件的元素。接著的 forEach 操作會印出通過述詞檢查的每個名稱。範例中的名稱維持英文:

import java.util.Arrays;
import java.util.List;

public class StreamLambdaExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Ava", "Mira", "Bo");
        names.stream()
             .filter(name -> name.length() > 2)
             .forEach(name -> System.out.println(name));
    }
}

這些 API 提供不同的迭代與處理方式;如果傳統迴圈更容易呈現控制流程,仍可能是較合適的選擇。

Runnable 常用來表示執行緒要執行的工作,因為它唯一的抽象方法不接收引數,也不回傳值。此 lambda 提供 Runnable 工作;建立 Thread 並不會執行工作,呼叫 start() 才會安排它獨立執行。範例中的輸出文字維持英文:

public class ThreadLambdaExample {
    public static void main(String[] args) {
        Runnable task = () -> System.out.println("Task running");
        Thread worker = new Thread(task);
        worker.start();
    }
}

執行緒的輸出相對於其他並行工作,可能以不同順序出現;排程也沒有保證。當目標介面和周遭程式碼能清楚表達行為時,再使用 lambda。它能減少語法,但不代表可以忽略 API 或並行行為。