Java Lambda 表達式

Lambda 表達式是 Java 8 引入的功能,提供簡潔的方式來實作函數式介面。

基本語法

(參數) -> 表達式
(參數) -> { 程式碼區塊 }

從匿名類別到 Lambda

// 匿名類別
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

// Lambda
Runnable r2 = () -> System.out.println("Hello");

各種形式

// 無參數
Runnable r = () -> System.out.println("Hello");

// 一個參數(可省略括號)
Consumer<String> c = s -> System.out.println(s);

// 多個參數
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;

// 帶有型別
BiFunction<Integer, Integer, Integer> add2 = (Integer a, Integer b) -> a + b;

// 多行程式碼
BiFunction<Integer, Integer, Integer> divide = (a, b) -> {
    if (b == 0) {
        throw new ArithmeticException("除數不能為 0");
    }
    return a / b;
};

函數式介面

Lambda 只能用於函數式介面(只有一個抽象方法):

@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
}

// 使用 Lambda
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;

System.out.println(add.calculate(5, 3));       // 8
System.out.println(multiply.calculate(5, 3));  // 15

更多說明請參考 Java 函數式介面

常用內建函數式介面

介面說明方法
Predicate<T>判斷boolean test(T t)
Function<T, R>轉換R apply(T t)
Consumer<T>消費void accept(T t)
Supplier<T>供應T get()
BiFunction<T, U, R>雙參數轉換R apply(T t, U u)
// Predicate:判斷
Predicate<Integer> isPositive = n -> n > 0;
System.out.println(isPositive.test(5));  // true

// Function:轉換
Function<String, Integer> length = s -> s.length();
System.out.println(length.apply("Hello"));  // 5

// Consumer:消費
Consumer<String> print = s -> System.out.println(s);
print.accept("Hello");

// Supplier:供應
Supplier<Double> random = () -> Math.random();
System.out.println(random.get());

更多說明請參考 Java 內建函數式介面

方法參考

更簡潔的語法:

// Lambda
Consumer<String> c1 = s -> System.out.println(s);

// 方法參考
Consumer<String> c2 = System.out::println;

四種方法參考

// 1. 靜態方法參考
Function<String, Integer> parse = Integer::parseInt;

// 2. 實例方法參考(特定物件)
String str = "Hello";
Supplier<Integer> len = str::length;

// 3. 實例方法參考(任意物件)
Function<String, Integer> length = String::length;

// 4. 建構子參考
Supplier<ArrayList<String>> supplier = ArrayList::new;

更多說明請參考 Java 方法參考

實際應用

集合操作

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 遍歷
names.forEach(name -> System.out.println(name));
names.forEach(System.out::println);

// 排序
names.sort((a, b) -> a.length() - b.length());

// 條件移除
names.removeIf(s -> s.startsWith("A"));

Stream

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 過濾偶數,乘以 2,求總和
int sum = numbers.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * 2)
    .reduce(0, Integer::sum);
// 60

排序

List<Person> people = getPeople();

// 依年齡排序
people.sort((p1, p2) -> p1.getAge() - p2.getAge());

// 或使用 Comparator
people.sort(Comparator.comparing(Person::getAge));
people.sort(Comparator.comparing(Person::getName).reversed());

執行緒

// Runnable
new Thread(() -> {
    System.out.println("執行緒執行中");
}).start();

// Callable
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
    return 42;
});

變數捕獲

Lambda 可以存取外部變數,但必須是 effectively final:

int multiplier = 10;  // effectively final

Function<Integer, Integer> multiply = n -> n * multiplier;

// multiplier = 20;  // 錯誤!不能修改

Lambda vs 匿名類別

特性Lambda匿名類別
語法簡潔冗長
this外部類別匿名類別本身
適用範圍函數式介面任何介面/類別
效能較好產生額外類別檔