Java Lambda詳解

Lambda表達式是JDK 8開始後的一種新語法形式。

作用:簡化匿名內部類的程式碼寫法

簡化格式

(匿名內部類被重寫方法的形參列表) -> {
	重寫方法
}

Lambda表達式只能簡化函數式介面的匿名內部類的寫法形式

什麼是函數式介面?

  • 首先必須是介面、其次介面中有且僅有一個抽象方法的形式
  • 通常會在介面上加上一個@FunctionalInterface註解,標記該介面必須是滿足函數式介面

如何使用Lambda?

我們將根據下面三個問題來幫助大家理解和使用Lambda

背景:我們自定義了一個man的類,創建了一個man的List。

class man {
    public int age;
    public char sex;
    public double socre;

    public man(int age, char sex, double score) {
        this.age = age;
        this.sex = sex;
        this.score = score;
    }
}

問題一

現需要對這個list根據人的年齡進行排序

要實現排序的功能,可以直接調用List對象自帶的sort方法完成,但是需要man先實現Comparator的介面並重寫compare方法,編譯器才能比較兩個不同man的大小。但是要更改原始類的程式碼,會比較麻煩,如果以後要對人的分數進行排序,那就又要更改的類的源碼,這樣操作很不方便。

sort(Comparator<? super E> c)方法可以直接傳入一個Comparator對象,我們可以直接改寫compare方法就可以實現比較。

第一種寫法

public class lambdaTry {
    public static void main(String[] args) {
        List<man> humans = new ArrayList<>();
        humans.add(new man(19, 'g', 98.0));
        humans.add(new man(18, 'b', 95.0));
        humans.add(new man(20, 'b', 96.0));
        humans.add(new man(17, 'g', 97.0));

        humans.sort(new Comparator<man>() {
            @Override
            public int compare(man o1, man o2) {
                return o1.age - o2.age;
            }
        });
    }
}

第二種寫法

Lambda

我們知道Lambda是用來簡化函數式介面的匿名內部類,且Comparator滿足函數式介面的兩個條件:

  • 首先必須是介面、其次介面中有且僅有一個抽象方法的形式
  • @FunctionalInterface註解
@FunctionalInterface
public interface Comparator<T> {
	int compare(T o1, T o2);
    ...
}

因此我們可以對上述的源碼進行改寫成Lambda格式

public class lambdaTry {
    public static void main(String[] args) {
        List<man> humans = new ArrayList<>();
        humans.add(new man(19, 'g', 98.0));
        humans.add(new man(18, 'b', 95.0));
        humans.add(new man(20, 'b', 96.0));
        humans.add(new man(17, 'g', 97.0));

        humans.sort((man o1, man o2) -> {
                return o1.age - o2.age;
        });
    }
}

改寫過後程式碼簡潔了很多。但是還可以繼續簡寫。

Lambda表達式的省略寫法

  1. 參數類型可以不寫
  2. 如果只有一個參數,參數類型可以省略,同時()也可以省略
  3. 如果Lambda表達式的方法塊中程式碼只有一行,可以省略大括弧,同時省略分號。
  4. 在條件三的基礎上,如果這行程式碼是return語句,必須省略return。

第三種寫法

Lambda簡寫

可以看到,此表達式滿足省略寫法的條件,可以繼續簡寫成如下格式。只需要一行語句就能完成

public class lambdaTry {
    public static void main(String[] args) {
        List<man> humans = new ArrayList<>();
        humans.add(new man(19, 'g', 98.0));
        humans.add(new man(18, 'b', 95.0));
        humans.add(new man(20, 'b', 96.0));
        humans.add(new man(17, 'g', 97.0));

        humans.sort((o1, o2) -> o1.age - o2.age);
    }
}

問題二

將List轉換為數組

我們知道List介面有一個方法toArray方法可以實現將其轉換為數組。

JDK11之後,提供了這樣的一個方法,提供了一個函數式介面來讓我們轉換

default <T> T[] toArray(IntFunction<T[]> generator) {
    return toArray(generator.apply(0));
}

IntFunction函數式介面是從JDK8之後實現的,內部只有一個apply抽象方法,是一個標準的函數式介面

@FunctionalInterface
public interface IntFunction<R> {
    R apply(int value);
}

我們可以直接用lambda,完成數組的轉換

public class lambdaTry {
    public static void main(String[] args) {
        List<man> humans = new ArrayList<>();
        humans.add(new man(19, 'g', 98.0));
        humans.add(new man(18, 'b', 95.0));
        humans.add(new man(20, 'b', 96.0));
        humans.add(new man(17, 'g', 97.0));

		// 原本寫法
        // man[] mans = humans.toArray(new IntFunction<man[]>() {
        //     @Override
        //     public man[] apply(int value) {
        //         return new man[value];
        //     }
        // });

        // lambda寫法
        man[] mans = humans.toArray(value -> new man[value]);
        
        // 實際上用不上這樣的寫法,只是為了舉例說明
        // man[] mans = humans.toArray(new man[0]);
        // man[] mans = humans.toArray(man[]::new);
        // 上面兩種寫法都可以,傳值進去的size為0不影響實際的轉換,具體可以看ArrayList的toArray重寫方法
        
    }
}

問題三

輸出年齡大於18的男同學的成績

可以用forEach方法快捷實現,forEach方法來自於Iterable介面

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

再看Consumer介面,也是一個函數式介面

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    ...
}

具體實現

public class lambdaTry {
    public static void main(String[] args) {
        List<man> humans = new ArrayList<>();
        humans.add(new man(19, 'g', 98.0));
        humans.add(new man(18, 'b', 95.0));
        humans.add(new man(20, 'b', 96.0));
        humans.add(new man(17, 'g', 97.0));

        // humans.forEach(new Consumer<>() {
        //     @Override
        //     public void accept(man man) {
        //         if (man.age >= 18 && man.sex == 'g') {
        //             System.out.println(man.score);
        //         }
        //     }
        // });

        humans.forEach(man -> {
            if (man.age >= 18 && man.sex == 'g') {
                System.out.println(man.score);
            }
        });
    }
}

有時Lambda還可以繼續簡寫成方法引用(method reference)

方法引用

方法引用通過方法的名字來指向一個方法。

方法引用可以使語言的構造更緊湊簡潔,減少冗餘程式碼。

方法引用使用一對冒號 ::

主要分為四種:

  • 構造器引用 Class::new

    man[] mans = humans.toArray(man[]::new);
    
  • 靜態方法引用 Class::static_method

    列印每個man(需要在man內重寫toString)

    humans.forEach(System.out::println)
    
  • 特定類的任意對象的方法引用 Class::method

  • 特定對象的方法引用 instance::method