函數(shù)式接口和Lambda表達(dá)式深入理解


我上一篇文章介紹了函數(shù)式接口和Lambda表達(dá)式,以及Java解決所謂的閉包。
這次深入一下。

0x00 函數(shù)式接口

前面講了一下函數(shù)式接口,不過可能只是講了個(gè)大概,大致講了一下什么是函數(shù)式接口

  • 函數(shù)式接口就是:一個(gè)interface,里面只有一個(gè)抽象方法,其他什么都沒有。
  • FunctionalInterface注解標(biāo)注一個(gè)函數(shù)式接口,不能標(biāo)注方法枚舉屬性這些。
  • 如果接口被標(biāo)注了@FunctionalInterface,這個(gè)類就必須符合函數(shù)式接口的規(guī)范
  • 即使一個(gè)接口沒有標(biāo)注@FunctionalInterface,如果這個(gè)接口滿足函數(shù)式接口規(guī)則,依舊被當(dāng)作函數(shù)式接口。

這次我們來(lái)用代碼來(lái)深入了解函數(shù)式接口

Demo01

如上圖,只包含一個(gè)抽象方法是最普通的函數(shù)式接口

兩個(gè)抽象方法,報(bào)錯(cuò)

再看,當(dāng)接口有兩個(gè)抽象方法的時(shí)候,就不在是函數(shù)式接口了,使用@FunctionalInterface標(biāo)注編譯時(shí)會(huì)報(bào)錯(cuò)

特例-01

奇怪的是這里有3個(gè)抽象方法,為什么不報(bào)錯(cuò)?
我們知道toStringequals方法是Object的方法,Java基礎(chǔ)告訴我們,Object是所有類的默認(rèn)父類,也就是說任何對(duì)象都會(huì)包含Object里面的方法,即使是函數(shù)式接口的實(shí)現(xiàn),也會(huì)有Object的默認(rèn)方法,所以:重寫Object中的方法,不會(huì)計(jì)入接口方法中,除了final不能重寫的,Object中所能重寫的方法,寫到接口中,不會(huì)影響函數(shù)式接口的特性

特例-02

Java8 允許接口中含有非抽象方法,這種在接口中使用default修飾的非抽象方法稱為默認(rèn)方法,默認(rèn)方法也不會(huì)影響函數(shù)式接口的特性。我們依然可以認(rèn)為DemoConsumer是一個(gè)函數(shù)式接口。

0x01 Lambda表達(dá)式深入

Lambda表達(dá)式的形式如下

(param1, param2, param3, param4…)->{ doing……};

由此引申出多種寫法:

//1.
() -> System.out.println("Hello Lambda");
//2.
number1 -> int a = number1 * 2;
//3.
(number1, number2) -> int a = number1 + number2;
//4.
(number1, number2) -> {
 int a = number1 + number2;
 System.out.println(a);
}

下面通過重構(gòu)一段代碼,來(lái)深入了解一下Lambda表達(dá)式

public class FunctionalInterfaceTest {
    public static void main(String[] args) {
        List<String> demoList = Arrays.asList( "Zing", "阿三", "小明", "小紅", "趙日天");
       rollCall(demoList);
    }
    public static void rollCall(List<? extends String> list){
        for(String name : list){
            if(name.startsWith("小")){
                System.out.println(name);
            }
        }
    }
}

如果希望篩選的條件能自由定義,而不是name.startsWith("小")寫死,并且希望找到人名后,不是簡(jiǎn)單的System.out.println(name);,而是能做一些其他的事情。
繼續(xù)重構(gòu):

/**
 * 函數(shù)式接口
 * @param <T>
 */
@FunctionalInterface
interface Checker<T extends String>{
    boolean check(T t);
}

@FunctionalInterface
interface Out<T>{
    void achievement(T t);
}

public class FunctionalInterfaceTest {
    /**
     * 點(diǎn)名
     */
    @Test
    public void testLambda() {
        List<String> demoList = Arrays.asList("小明", "Zing", "阿三", "小紅", "趙日天");
        rollCall(demoList,
                name-> name.startsWith("Z"),
                name->{
                    String rate = name + "是單身狗!";
                    System.out.println(rate);
                });
    }

    /**
     * 點(diǎn)名邏輯
     * @param list
     * @param checker
     */
    public void rollCall(List<? extends String> list, Checker checker,Out out){
        for(String name : list){
            if(checker.check(name)){
                out.achievement(name);
            }
        }
    }
}

運(yùn)行結(jié)果

一不小心暴露了什么。哈哈哈

通過上面的重構(gòu),很明顯,這么寫也是合法的

        Checker checker =  name-> name.startsWith("Z"),
        Out estimator = name->{
            String rate = name + "是單身狗!";
            System.out.println(rate);
        };

由此可以知道,Lambda和函數(shù)式接口是等價(jià)的。

0x02 補(bǔ)充

  • 類型
    有人會(huì)很奇怪,為什么Checker checker = name-> name.startsWith("Z")這樣寫的時(shí)候,name會(huì)被當(dāng)成String 類型?

這是Java的類型推斷,大致邏輯是編譯器知道函數(shù)式接口方法的輸入?yún)?shù)類型,所以無(wú)論前面的參數(shù)是什么名字,都會(huì)被當(dāng)成方法所需要的參數(shù)類型。

  • 簡(jiǎn)單縮寫
    還有一個(gè)奇怪的地方name->name.startsWith("Z")為什么這樣寫也可以?
    為什么不是寫成``name->{ return name.startsWith("Z");}` 。
    很明顯,后面的寫法是沒有錯(cuò)的,

但是Idea會(huì)有一個(gè)虛線,說明不需要寫return


展開看說明

當(dāng)只需要執(zhí)行一條語(yǔ)句的時(shí)候,lambda支持這種簡(jiǎn)潔返回。所以為什么拒絕呢?

  • 外部參數(shù)
    Lambda表達(dá)式是不能操作外部對(duì)象的,因?yàn)長(zhǎng)ambda 實(shí)質(zhì)上是接口的子對(duì)象,只能訪問靜態(tài)資源和本身的內(nèi)部變量。

報(bào)錯(cuò)!

編譯器會(huì)要求將外部變量使用final修飾。

  • 和方法引用結(jié)合
    方法引用Method References是Java8配合Lambda一起做出的新特性,當(dāng)Lambda表達(dá)式里面只執(zhí)行已知的方法的時(shí)候,可以使用方法引用來(lái)寫出跟簡(jiǎn)潔易讀的代碼
List<String> demoList = Arrays.asList("小明", "Zing", "阿三", "小紅", "趙日天");
demoList.forEach(System.out::println);

看到這里想必心里不禁想說,我擦,好簡(jiǎn)潔!
官方給出了4種方法引用

Kinds of Method References

| Kind | Example|
| ---- |----|
|Reference to a static method|ContainingClass::staticMethodName|
|Reference to an instance method of a particular object|containingObject::instanceMethodName|
|Reference to an instance method of an arbitrary object of a particular type|ContainingType::methodName|
|Reference to a constructor| ClassName::new|
來(lái)源:
http://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

我想我就不用翻譯了吧,出門百度翻譯??


love&peace
FS全棧計(jì)劃目錄:https://micorochio.github.io/fs-plan/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容