【Java8】Java8實(shí)戰(zhàn)之行為參數(shù)化與Lambda

Java8實(shí)戰(zhàn)之行為參數(shù)化與Lambda

前言

現(xiàn)在Java的迭代速度比以前快了很多,然而,本渣渣最近才開始學(xué)習(xí)Java8,相比于之前的版本,Java8中引入了許多新的特性,其中最主要的是LambdaStream、Optional<T>、新的時間日期API,接下來將分成幾個小節(jié),分別學(xué)習(xí)這些內(nèi)容,并且將學(xué)習(xí)筆記整理出來,參考書籍為《Java8實(shí)戰(zhàn)》。

本小節(jié)主要學(xué)習(xí)行為參數(shù)化以及Lambda

行為參數(shù)化

在Java8之前,Java中的方法或者說函數(shù)是二等公民,也就是說,方法或者函數(shù)是無法作為參數(shù)進(jìn)行傳遞的,隨著編程思想的逐步發(fā)展,函數(shù)式編程的思想越來越受到重視,其優(yōu)勢也逐漸凸顯出來,Java8引入了對應(yīng)的解決方法,提供了一種類似的方式來處理,使得可以將函數(shù)作為參數(shù)進(jìn)行傳遞,從而實(shí)現(xiàn)了類似函數(shù)式編程的功能,亦即Lamda。

所謂的形式參數(shù)化,就是將行為,通常表現(xiàn)為函數(shù)或者方法做為參數(shù)進(jìn)行傳遞,這種編程方式的好處在于,可以將變化的部分抽取出來,形成函數(shù),然后根據(jù)情況傳遞實(shí)現(xiàn)的內(nèi)容,使得整體具有更高的靈活性。

下面的舉例內(nèi)容大致來自于書中的例子,通過該例子,可以看出行為參數(shù)化的優(yōu)勢

class Apple {
    private int weight;
    private String color;

    // 省略set、get方法
}

對于上面的Apple類,如果我們想根據(jù)不同的情況進(jìn)行篩選,實(shí)現(xiàn)方式有多種,一種是根據(jù)需要篩選的條件提供對應(yīng)的篩選方法,如下所示

public List<Apple> filterAppleByWeight(List<Apple> apples) {
        List<Apple> result = new ArrayList<>();
        for (Apple apple : apples) {
            if (apple.getWeight() > 30) {
                result.add(apple);
            }
        }
        return result;
    }

public List<Apple> filterAppleByColor(List<Apple> apples) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (apple.getColor().equals("green")) {
            result.add(apple);
        }
    }
    return result;
}

上面的方式看起來沒有什么問題,而且非常直觀,當(dāng)需要增加新的過濾條件時,只需要復(fù)制一份,然后更改過濾條件就行,但實(shí)際上這種方式問題是比較大的

  • 存在著明顯的代碼冗余,代碼冗余就意味著如果需要修改,需要注意的地方會比較多
  • 不利于變化,比如說現(xiàn)在需要過濾weight > 30 && color == "green"的,那么就需要為其再編寫對應(yīng)的處理函數(shù),而改變的僅僅是其中的一行代碼。尤其是當(dāng)屬性比較多的時候,各種情況組合起來,需要考慮的情況非常多,代碼也會變得非常復(fù)雜。

由于上面的方式比較難以應(yīng)對各種情況,所以,更好地方式是采用類似策略模式的形式,將其中可能經(jīng)常變化的部分抽取出來放在接口里面,在運(yùn)行時通過傳入不同的實(shí)現(xiàn)類來處理

// 過濾器接口
interface AppleFilter {
    boolean filter(Apple apple);
}

// 過濾器實(shí)現(xiàn)
class GreenAppleFilter implements AppleFilter {

    @Override
    public boolean filter(Apple apple) {
        return "green".equals(apple.getColor());
    }
}

// 過濾蘋果操作
public List<Apple> static filterApple(List<Apple> apples, AppleFilter appleFilter) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (appleFilter.filter(apple)) {
            result.add(apple);
        }
    }
    return result;
}

public void operation() {
    AppleFilter appleFilter = new GreenAppleFilter();
    filterApple(apples, appleFilter);
}

通過上面的方式,可以在需要的時候創(chuàng)建對應(yīng)的過濾器實(shí)現(xiàn)類,然后傳遞給過濾操作函數(shù)即可,通過這種方式,可以避免前面出現(xiàn)的修改問題。

然而,上面的這種方式其實(shí)并不優(yōu)雅,也不方便,由于我們需要根據(jù)不同的情況,編寫不同的實(shí)現(xiàn)類,這雖然方便解耦,但卻增大了不必要的工作量。

通過匿名內(nèi)部類的形式,我們就可以在不編寫對應(yīng)實(shí)現(xiàn)類的情況下,直接將對應(yīng)的實(shí)現(xiàn)傳遞給對應(yīng)的方法,所以,上面的代碼可以直接簡化為

public void operation() {
        // 匿名內(nèi)部類實(shí)現(xiàn)
    filterApple(apples, new AppleFilter() {
        @Override
        public boolean filter(Apple apple) {
            return "green".equals(apple.getColor());
        }
    });
}

這樣就可以不用編寫實(shí)現(xiàn)類,然后再來實(shí)現(xiàn)對應(yīng)的操作了,這種方式已經(jīng)是非常優(yōu)雅了,不過,仍然不是最優(yōu)雅的方式,因?yàn)槲覀冞€需要new一個對象,然后實(shí)現(xiàn)其方法。

上面的實(shí)現(xiàn)方式是在Java8之前的方式,在Java8之后,通過Lambda,我們可以將上面的方式更佳地簡化,最終代碼如下所示

public void operation() {
    filterApple(apples, apple -> "green".equals(apple.getColor()));
}

可以看到,Lambda表達(dá)式以及其簡潔的方式來實(shí)現(xiàn)我們的需求,并且滿足了對改動等的要求,所以,下面我們就詳細(xì)地學(xué)習(xí)Lambda表達(dá)式。

Lambda表達(dá)式

Lambda表達(dá)式是在Java8之后引入的一種新的代碼編寫方式,通過Lambda表達(dá)式,可以在某種程度上使得代碼變得更加簡潔,尤其是在某個函數(shù)只需要使用一次的時候,無需再為他們設(shè)計(jì)專門的類以及方法。

Lambda表達(dá)式可以簡單地理解匿名函數(shù),所以Lambda表達(dá)式具有函數(shù)除了名字之外的的所有特性

  • 可以有一個或者多個參數(shù)
  • 可以具有返回值也可以沒有
  • 具有方法體,也就是Lambda所要執(zhí)行的內(nèi)容

將Lambda表達(dá)式理解為匿名函數(shù)之后,關(guān)于Lambda表達(dá)式的寫法就比較好理解了,Lambda具有兩種最基本的寫法

// 表達(dá)式
() -> EXP

// 語句
() -> {STATEMENT1; STATEMENT2; ...}

其中的->用于分隔參數(shù)以及方法體,是必須具備的,如果只有一個參數(shù),()可以不用,如apple -> {}(apple) -> {}是等價的,而且,在Lambda表達(dá)式參數(shù)中,可以不用寫上類型,原因在于lambda可以根據(jù)使用的場景進(jìn)行推導(dǎo),后面展開。

注意上面的兩種形式,其中的表達(dá)式?jīng)]有{},并且不需要;只能有一個表達(dá)式(表達(dá)式的返回值就是Lambda的值),而對于語句來講可以有多個表達(dá)式,每個表達(dá)式后面需要有;,最后一個表達(dá)式作為Lambda表達(dá)式的返回值。

函數(shù)接口

上面我們看到了Lambda的寫法,接下來我們來看下Lambda表達(dá)式的應(yīng)用場景,并不是說每個Lambda表達(dá)式都可以作為參數(shù)傳遞到任意的方法中,Lambda表達(dá)式需要符合某種規(guī)則(參數(shù),返回值),而這些規(guī)則,就來自于函數(shù)接口。

函數(shù)接口是一種特別的接口,這種接口是專門為Lambda表達(dá)式創(chuàng)建的,其特性表現(xiàn)為

  • 只能有一個抽象方法(可以有多個默認(rèn)方法,關(guān)于默認(rèn)方法,將在后面的小節(jié)學(xué)習(xí)到)
  • 可以使用@FunctionalInterface,進(jìn)行修飾

比如上面的AppleFilter就是一個函數(shù)接口,而下面的接口就不是了,因?yàn)橛胁恢挂粋€抽象方法

interface AppleFilter {
    boolean filter(Apple apple);
    boolean match(Apple apple);
} 

在傳遞Lambda表達(dá)式的時候,在方法的參數(shù)中,需要提供一個函數(shù)接口,才能將Lambda表達(dá)式傳遞給該方法,并且該Lambda表達(dá)式必須與函數(shù)接口中的抽象方法簽名一致,可以簡單地理解為該Lambda表達(dá)式就是該接口的一個實(shí)現(xiàn)。

public void operation() {
    filterApple(apples, apple -> "green".equals(apple.getColor()));
    // 其中的Lambda表達(dá)式 apple -> "green".equals(apple.getColor());
    // 跟接口中的抽象方法簽名 boolean filter(Apple apple); 是一致的
    // 一個參數(shù),一個Boolean類型的返回值
}

同時我們也提到了Lambda表達(dá)式參數(shù)可以不用寫類型,可以根據(jù)情況進(jìn)行推導(dǎo),其原因也在于此,當(dāng)我們把一個Lambda表達(dá)式傳遞給一個方法的時候,由于該方法中的函數(shù)接口中的匿名方法簽名已經(jīng)是確定的了,比如上面的boolean filter(Apple apple),所以,當(dāng)使用Lambda表達(dá)式 apple -> "green".equals(apple.getColor())的時候,編譯器可以根據(jù)方法簽名中的方法類型來推導(dǎo)出Lambda表達(dá)式中的參數(shù)簽名,進(jìn)而對后面的內(nèi)容進(jìn)行驗(yàn)證,參數(shù)個數(shù)以及返回值也是如此,而且可以驗(yàn)證整個Lambda表示式是否是符合對應(yīng)抽象方法簽名的。

這里有個地方需要注意,同一個Lambda表示式可以用在不同的函數(shù)接口的,只需要符合其方法簽名即可,同時,一個Lambda表達(dá)式可以直接賦值給其對應(yīng)的函數(shù)接口對象

interface AppleFilter {
    boolean filter(Apple apple);
}

interface Predict {
    boolean test(Apple apple);
}

// 上面的Lambda表達(dá)式可以用于這兩個接口
// 如果結(jié)合泛型的話,則同一個Lambda表達(dá)式可以使用的場景就更多了

由于如果每次需要,都要設(shè)計(jì)對應(yīng)的函數(shù)接口,也是一件非常麻煩的事情,所以,在JDK中提供了基本上涵蓋了我們所需要的函數(shù)接口了,可以根據(jù)需要使用即可,其中常用的幾個如下所示

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

public interface Function<T, R> {
    R apply(T t);
}

public interface Supplier<T> {
    T get();
}

public interface Predicate<T> {
    boolean test(T t);
}

// 更多的函數(shù)接口可以參考 package java.util.function

總結(jié)

本小節(jié)主要學(xué)習(xí)了行為參數(shù)化的作用以及意義,Lambda表達(dá)式的寫法,函數(shù)接口的限制以及作用,常見的幾個函數(shù)接口。關(guān)于Lambda表達(dá)式的寫法,多練習(xí)幾次就熟悉了,在Java8中引入的其他幾個新功能,基本都是構(gòu)建在Lambda表示式之上的,所以,目前來說,對Lambda表示式只需要有個概念性的理解即可,后面經(jīng)過Stream的練習(xí)之后,就會熟悉Lambda表達(dá)式了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java8 in action 沒有共享的可變數(shù)據(jù),將方法和函數(shù)即代碼傳遞給其他方法的能力就是我們平常所說的函數(shù)式...
    鐵牛很鐵閱讀 1,358評論 1 2
  • 原文鏈接: Lambdas 原文作者: shekhargulati 譯者: leege100 lambda表達(dá)式是...
    忽來閱讀 6,749評論 8 129
  • 注:之前關(guān)于Java8的認(rèn)知一直停留在知道有哪些修改和新的API上,對Lambda的認(rèn)識也是僅僅限于對匿名內(nèi)部類的...
    mualex閱讀 2,937評論 1 4
  • lambda表達(dá)式(又被成為“閉包”或“匿名方法”)方法引用和構(gòu)造方法引用擴(kuò)展的目標(biāo)類型和類型推導(dǎo)接口中的默認(rèn)方法...
    183207efd207閱讀 1,549評論 0 5
  • Anna艷娜 2018年5月11日復(fù)盤 六點(diǎn)過起床,最后收拾了下東西,早早出門,擔(dān)心拉著箱子會很擠。上班高峰期,地...
    Anna艷娜閱讀 180評論 0 0

友情鏈接更多精彩內(nèi)容