Lambda 表達式

Lambda 管中窺豹

能夠把 Lambda 表達式理解爲簡潔地可傳遞的匿名函數的一種方式:沒有名稱,有參數列表、函數主體、返回類型和可能的異常。理論上講,匿名函數作不到的事,Lambda 也作不了。後者只是讓前者可讀性更強,寫得更輕鬆。java

回顧上一章最後的那個 Lambda 表達式express

(Apple apple1) -> "green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()

咱們能夠發現 Lambda 能夠劃分爲三個部分:app

  • 參數 (Apple apple1)
  • 箭頭 ->
  • 主體 "green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()

Lambda 的基本語法是這樣的:ide

  • (parameters) -> expression
  • (parameters) -> { statements; }

在哪裏以及如何使用 Lambda

咱們能夠在函數式接口中使用 Lambda。函數

函數式接口就是隻定義一個抽象方法的接口。學習

例如上一章的蘋果謂詞接口測試

public interface ApplePredicate {
    boolean test(Apple apple);
}

只有一個抽象方法的接口能讓 Lambda 把整個表達式做爲函數式接口的實例。匿名內部類同樣能夠完成一樣的事,只不過很笨拙。設計

JDK 中的 Runnable 接口code

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

兩種實現方式對象

Runnable runnable1 = () -> System.out.println("Hello Word!");
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello Word!");
            }
        };

這兩鍾實現結果是同樣的。函數式接口的返回類型基本上就是 Lambda 的返回類型。

函數式接口通常均可以被 @FunctionalInterface 註解,這個註解就如同它的名字同樣表明這個接口是函數式接口。而且它和 @Override 同樣只是讓編譯期判斷是否正確,運行期無關,並非必需的。若是在一個接口中定義了多個抽象方法,並加上這個註解再編譯的話,編譯器便會給你的報錯,由於這樣的接口已經不符合函數式接口的定義了。

使用函數式接口

JDK 自己也自帶了幾個函數式接口,好比 Predicate、Consumer、Function。咱們可使用一下練練手。

Predicate

這個和上一章最後咱們本身寫的那個函數式接口,二者徹底同樣,都是用來作條件測試的謂詞接口。

經過謂詞過濾泛型集合

public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T t : list) {
            if (predicate.test(t)) {
                result.add(t);
            }
        }
        return result;
    }

過濾掉字符串集合中空字符串

List<String> stringList = new ArrayList<>();
        stringList.add("hello");
        stringList.add("");
        stringList.add("lambda");

        // Lambda 實現
        Predicate<String> stringPredicate = (String s) -> !s.isEmpty();
        // 匿名實現
        Predicate<String> stringPredicate1 = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return !s.isEmpty();
            }
        };
        List<String> result = filter(stringList, stringPredicate);

這樣空字符串就會被過濾掉,只剩下 hellolambda

Consumer

這個函數式接口是用來接收一個對象並對其進行處理。

遍歷一個泛型集合

public static <T> void forEach(List<T> list, Consumer<T> consumer) {
        for (T t : list) {
            consumer.accept(t);
        }
    }

取出字符串集合裏面的對象並打印輸出

// Lambda 實現
        Consumer<String> consumer = (String s) -> System.out.println(s);
        // 匿名實現
        Consumer<String> consumer1 = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        forEach(stringList, consumer);

這樣就會打印輸出 hello、 、lambda

Function

這個函數式接口是用來接收一個對象並映射到另外一個對象。

接收一個集合對象並返回另外一個集合對象

public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
        List<R> result = new ArrayList<>();
        for (T t : list) {
            result.add(function.apply(t));
        }
        return result;
    }

接收一個字符串集合並映射成其長度的整型集合後返回

// Lambda 實現
        Function<String, Integer> function = (String s) -> s.length();
        // 匿名實現
        Function<String, Integer> function1 = new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return s.length();
            }
        };
        List<Integer> integerList = map(stringList, function);

這樣 hello、 、lambda 就分別對應其長度 506

方法引用

方法引用能夠看做對特定 Lambda 的一種快捷寫法,本質上依然是 Lambda。它的基本思想是,若是一個 Lambda 表明的僅僅是 直接調用 這個方法而不是 描述如何去調用 這個方法,那最好仍是用名稱來調用它。

例如在上一章的蘋果實例中咱們須要用謂詞判斷是否成熟

// 普通 Lambda 寫法
        Predicate<Apple> applePredicate1 = (Apple apple) -> apple.isAging();
        // 方法引用寫法
        Predicate<Apple> applePredicate2 = Apple::isAging;

咱們能夠把方法引用看做對 僅僅涉及單一方法 的 Lambda 的語法糖。

構造函數引用

對於一個現有的構造函數,咱們能夠利用它的名稱和 new 來建立它的一個引用:ClassName::new

// 普通 Lambda 建立對象
        // Supplier<Apple> appleSupplier = () -> new Apple();
        // 構造函數引用建立無參對象
        Supplier<Apple> appleSupplier = Apple::new;
        // 獲取實例
        Apple apple1 = appleSupplier.get();
        
        // 構造函數引用建立有一個參數對象
        Function<String, Apple> appleFunction = Apple::new;
        // 獲取實例
        Apple apple2 = appleFunction.apply("red");

        // 構造函數引用建立有兩個參數對象
        BiFunction<String, Integer, Apple> appleBiFunction = Apple::new;
        // 獲取實例
        Apple apple3 = appleBiFunction.apply("red", 1);

那麼當參數有不少的時候怎麼辦呢?咱們能夠自定義一個函數式接口進行處理。

三個參數的構造函數引用接口

public interface TriFunction<T, U, V, R> {
    R apply(T t, U u, V v);
}

調用也是相似的

// 構造函數引用建立有三個參數對象
        TriFunction<String, Integer, Boolean, Apple> appleTriFunction = Apple::new;
        // 獲取實例
        Apple apple4 = appleTriFunction.apply("red", 1, true);

Lambda 和方法引用實戰

傳遞代碼

咱們若是要對一個蘋果集合按照重量從小到大排序,首先確定要進行判斷大小,而後對其進行排序。按照咱們已經通過一章多的學習,應該能很輕鬆地構建一個解決方案。

一、建立比較器

public class AppleComparator implements Comparator<Apple> {
    @Override
    public int compare(Apple o1, Apple o2) {
        return o1.getWeight().compareTo(o2.getWeight());
    }
}

二、調用 List 已經實現好的排序方法

public class Main {
    public static void main(String[] args) {
        List<Apple> appleList = new ArrayList<>();

        // 重量爲2的成熟紅蘋果 No.1
        Apple apple = new Apple();
        apple.setColor("red");
        apple.setWeight(2);
        apple.setAging(true);
        appleList.add(apple);

        // 重量爲1的未成熟綠蘋果 No.2
        apple = new Apple();
        apple.setColor("green");
        apple.setWeight(1);
        apple.setAging(false);
        // 如今 appleList 的順序是放入的順序 No.一、No.2
        appleList.add(apple);

        // 依照重量排序後 appleList 的順序會變成 No.二、No.1
        appleList.sort(new AppleComparator());
    }
}

這只是簡單的一個經過傳遞比較器來進行排序。下面咱們會用匿名類來實現上一章學習的 應對不斷變化的需求

使用匿名類

到這一步其實已經算得上符合正常軟件工程設計了,能夠捨去 AppleComparator 這樣的實現方式。

appleList.sort(new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o1.getWeight().compareTo(o2.getWeight());
            }
        });

使用 Lambda 表達式

緊接着咱們能夠更加高效地用 Lambda 實現。

appleList.sort((Apple o1, Apple o2) -> o1.getWeight().compareTo(o2.getWeight()));

咱們還能夠進一步簡化代碼。Java 編譯器會從目標類型自動推斷出適合 Lambda 的返回類型。所以能夠省略對傳入參數的類型定義。

// appleList 的類型定義是 List<Apple>,傳遞進 sort() 的 Comparator<T> 會自動定義泛型爲 Apple,因此 Lambda 也能夠自動推斷爲 Comparator 的 compare() 傳入的類型。
        appleList.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight()));

使用方法引用

使用方法引用還能夠更加讓人通俗易懂。

Comparator 有個靜態方法 comparing 能夠接收 Function 函數式接口,用做比較依據。

// 傳入的 Function 本質是將蘋果(Apple)映射成了蘋果的重量(Integer)
        // Function<Apple, Integer> function = (Apple o1) -> o1.getWeight();
        // 方法引用後
        Function<Apple, Integer> function = Apple::getWeight;
        // 傳入到 comparing 靜態方法
        Comparator.comparing(function);

因此最後能夠簡化到極致

// 表明按照蘋果的重量進行比較後排序
        appleList.sort(Comparator.comparing(Apple::getWeight));

複合 Lambda 表達式的有用方法

Java 8提供了容許進行復合的方法。好比咱們可讓兩個謂詞之間作 or 操做,組成一個更增強大的謂詞。

比較器複合

若是想要從大到小排序蘋果的重量(默認的 sort() 是從小到大排序)

appleList.sort(Comparator.comparing(Apple::getWeight).reversed());

可是若是兩個的蘋果重量同樣,咱們須要再根據顏色或者是否成熟來排序呢?

咱們可使用 thenComparing 方法

appleList.sort(Comparator
                // 從大到小排列蘋果的重量
                .comparing(Apple::getWeight).reversed()
                // 而後按照顏色字母順序
                .thenComparing(Apple::getColor)
                // 而後按照是否成熟
                .thenComparing(Apple::getAging));

這樣就能夠建立一個比較器鏈了。

謂詞複合

謂詞接口包括三個方法:negate、and 和 or,咱們能夠以此建立複雜的謂詞。

選出蘋果不是紅的

Predicate<Apple> isRed = (Apple o1) -> "red".equalsIgnoreCase(o1.getColor());
        Predicate<Apple> noRed = isRed.negate();

選出蘋果不是紅的且成熟的

Predicate<Apple> noRedAndIsAging = noRed.and(Apple::getAging);

選出蘋果不是紅的且成熟的或重量大於1

Predicate<Apple> noRedAndIsAgingOrHeavey = noRedAndIsAging.or((Apple o1) -> o1.getWeight() > 1);

總結起來,除了 isRed 謂詞要在第一步寫,其他的地方均可以一句話寫完

Predicate<Apple> predicate = noRed
                .and(Apple::getAging)
                .or((Apple o1) -> o1.getWeight() > 1);

這樣從簡單的 Lambda 出發,能夠構建更加複雜的表達式,但讀起來會更加輕鬆。注意,and 和 or 的是按照鏈中的位置執行。

函數複合

Function 接口包括兩個方法:andThen 和 compose,它們都會返回一個 Function 的實例。

咱們能夠用 Function 定義三個函數 f(x)、g(x)和 g(x),先看看 andThen()

// f(x) = x + 1
        Function<Integer, Integer> f = x -> x + 1;
        // g(x) = x * 2
        Function<Integer, Integer> g = x -> x * 2;
        // h(x) = f(g(x))
        Function<Integer, Integer> h = f.andThen(g);

傳入 x 進行運算

// 結果爲4
        int result = h.apply(1);

compose()

// h(x) = g(f(x))
        h = f.compose(g);
        // 結果爲3
        result = h.apply(1);

第三章的東西有點多,須要反覆消化理解。

Java 8 實戰 第三章 Lambda 表達式 讀書筆記

歡迎加入咖啡館的春天(338147322)。

相關文章
相關標籤/搜索