Java8中的函數(shù)式接口

最近看了慕課網(wǎng)的《SpringBoot2.0不容錯(cuò)過的新特性 WebFlux響應(yīng)式編程》教程,里面介紹了關(guān)于Java8函數(shù)式接口的相關(guān)知識(shí)??赐暧X得意猶未盡,找了幾篇博客對(duì)比學(xué)習(xí)。這里個(gè)人記錄一下關(guān)于這邊面的一些知識(shí)。
大部分內(nèi)容轉(zhuǎn)自:必看:深入學(xué)習(xí)Java8中的函數(shù)式接口

函數(shù)式接口

函數(shù)式接口(Functional Interface)就是一個(gè)有且僅有一個(gè)抽象方法,但是可以有多個(gè)非抽象方法的接口。
函數(shù)式接口可以被隱式轉(zhuǎn)換為 lambda 表達(dá)式。

函數(shù)式接口用@FunctionalInterface 注解標(biāo)識(shí)。

函數(shù)接口有三條重要法則:
  1. 一個(gè)函數(shù)接口只有一個(gè)抽象方法。
  2. 在 Object 類中屬于公共方法的抽象方法不會(huì)被視為單一抽象方法。
    函數(shù)接口可以有默認(rèn)方法和靜態(tài)方法。
  3. 任何滿足單一抽象方法法則的接口,都會(huì)被自動(dòng)視為函數(shù)接口。這包括 Runnable 和 Callable 等傳統(tǒng)的接口,以及您自己構(gòu)建的自定義接口。

怎么理解,看幾個(gè)例子。

比如:你聲明一個(gè)接口:

image

這會(huì)編譯錯(cuò),編譯器會(huì)告訴你no target method。而如果加一個(gè)方法:

image

這就OK了,一個(gè)函數(shù)式接口聲明好了。再加一個(gè)呢?

image

不ok,明確說了只有一個(gè)抽象方法嘛。但是如果換一種函數(shù)簽名:

image

錯(cuò)誤依舊,因?yàn)檫@個(gè)方法簽名是Object類的public方法。而再改一下:

image

這就OK了。一個(gè)抽象方法,一個(gè)Object的public方法,相安無事。Object還有其他方法,clone方法試試會(huì)怎么樣?

image

這又不行了,因?yàn)榍懊婷鞔_說了,要是Object的public方法,而clone是protected的。

所以總結(jié)一句話就是:

函數(shù)式接口,有且僅有一個(gè)抽象方法,Object的public方法除外。

因?yàn)镴ava本身支持多接口實(shí)現(xiàn),你定義一個(gè)Class可以implements多個(gè)interface。所以這個(gè)限制也沒什么影響,如果想約定一個(gè)函數(shù)式接口來統(tǒng)一,也可以做一些默認(rèn)的實(shí)現(xiàn)來達(dá)到一個(gè)接口多個(gè)抽象方法的目的,比如下面這種做法:

一個(gè)普通接口NonFunc:

image

函數(shù)式接口Func:

image

實(shí)現(xiàn)的測(cè)試類:

image

函數(shù)式接口的一大特性就是可以被lambda表達(dá)式和函數(shù)引用表達(dá)式代替。也就是說聲明這樣的接口,是可以靈活的以方法來傳參??磦€(gè)例子:

image

上面例子列舉了一個(gè)lambda模式和一個(gè)方法引用模式,這樣就可以利用函數(shù)式編程強(qiáng)大的能力,將方法作為參數(shù)了。

JDK 1.8 新增加的函數(shù)接口:

java.util.function 它包含了很多類,用來支持 Java的 函數(shù)式編程,主要包含幾大類:Function、Predicate、Supplier、Consumer和*Operator(沒有Operator接口,只有類似BinaryOperator這樣的接口)。

Function 接口

關(guān)于Function接口,其接口聲明是一個(gè)函數(shù)式接口,其抽象表達(dá)函數(shù)為

image

函數(shù)意為將參數(shù)T傳遞給一個(gè)函數(shù),返回R。即R=Function(T)
其默認(rèn)實(shí)現(xiàn)了2個(gè)default方法,分別是compose、andThen。若FunctionA. compose(FunctionB),先執(zhí)行FunctionB,再FunctionA。FunctionA. andThen(FunctionB),先執(zhí)行FunctionA,再FunctionB。則看個(gè)例子:

public static void main(String[] args) {
        Function<Integer,Integer> incr= x->x+1;
        Function<Integer,Integer> multiply=x->x*2;
        int x=3;
        // 3*2+1
        System.out.println("compose:  "+incr.compose(multiply).apply(x));
        // (3+1)*2
        System.out.println("andThen:  "+incr.andThen(multiply).apply(x));
    }
// 打印如下:
//    compose:  7
//    andThen:  8

高階函數(shù)

只是普通的lambda表達(dá)式,其能力有限。我們會(huì)希望引入更強(qiáng)大的函數(shù)能力——高階函數(shù),可以定義任意同類計(jì)算的函數(shù)。
高階函數(shù):就是返回函數(shù)的函數(shù)。

比如這個(gè)函數(shù)定義,參數(shù)是z,返回值是一個(gè)Function,這個(gè)Function本身又接受另一個(gè)參數(shù)y,返回z+y。于是我們可以根據(jù)這個(gè)函數(shù),定義任意加法函數(shù):

image

由于高階函數(shù)接受一個(gè)函數(shù)作為參數(shù),結(jié)果返回另一個(gè)函數(shù),所以是典型的函數(shù)到函數(shù)的映射。
BiFunction提供了二元函數(shù)的一個(gè)接口聲明,舉例來說:

image

其輸出結(jié)果將是:f(z)=x*y, when x=3,y=5, then f(z)=15。

二元函數(shù)沒有compose能力,只是默認(rèn)實(shí)現(xiàn)了andThen。

有了一元和二元函數(shù),那么可以通過組合擴(kuò)展出更多的函數(shù)可能。

Function接口相關(guān)的接口包括:

BiFunction :R apply(T t, U u);接受兩個(gè)參數(shù),返回一個(gè)值,代表一個(gè)二元函數(shù);

DoubleFunction :R apply(double value);只處理double類型的一元函數(shù);

IntFunction :R apply(int value);只處理int參數(shù)的一元函數(shù);

LongFunction :R apply(long value);只處理long參數(shù)的一元函數(shù);

ToDoubleFunction:double applyAsDouble(T value);返回double的一元函數(shù);

ToDoubleBiFunction:double applyAsDouble(T t, U u);返回double的二元函數(shù);

ToIntFunction:int applyAsInt(T value);返回int的一元函數(shù);

ToIntBiFunction:int applyAsInt(T t, U u);返回int的二元函數(shù);

ToLongFunction:long applyAsLong(T value);返回long的一元函數(shù);

ToLongBiFunction:long applyAsLong(T t, U u);返回long的二元函數(shù);

DoubleToIntFunction:int applyAsInt(double value);接受double返回int的一元函數(shù);

DoubleToLongFunction:long applyAsLong(double value);接受double返回long的一元函數(shù);

IntToDoubleFunction:double applyAsDouble(int value);接受int返回double的一元函數(shù);

IntToLongFunction:long applyAsLong(int value);接受int返回long的一元函數(shù);

LongToDoubleFunction:double applyAsDouble(long value);接受long返回double的一元函數(shù);

LongToIntFunction:int applyAsInt(long value);接受long返回int的一元函數(shù);

Operator

Operator其實(shí)就是Function,函數(shù)有時(shí)候也叫作算子。算子在Java8中接口描述更像是函數(shù)的補(bǔ)充,和上面的很多類型映射型函數(shù)類似。

算子Operator包括:UnaryOperator和BinaryOperator。分別對(duì)應(yīng)單元算子和二元算子。

算子的接口聲明如下:

image

二元算子的聲明:

image

很明顯,算子就是一個(gè)針對(duì)同類型輸入輸出的一個(gè)映射。在此接口下,只需聲明一個(gè)泛型參數(shù)T即可。對(duì)應(yīng)上面的例子:

image

例子里補(bǔ)充一點(diǎn)的是,BinaryOperator提供了兩個(gè)默認(rèn)的static快捷實(shí)現(xiàn),幫助實(shí)現(xiàn)二元函數(shù)min(x,y)和max(x,y),使用時(shí)注意的是排序器可別傳反了:)

其他的Operator接口:(不解釋了)

LongUnaryOperator:long applyAsLong(long operand);

IntUnaryOperator:int applyAsInt(int operand);

DoubleUnaryOperator:double applyAsDouble(double operand);

DoubleBinaryOperator:double applyAsDouble(double left, double right);

IntBinaryOperator:int applyAsInt(int left, int right);

LongBinaryOperator:long applyAsLong(long left, long right);

Predicate

predicate是一個(gè)謂詞函數(shù),主要作為一個(gè)謂詞演算推導(dǎo)真假值存在,其意義在于幫助開發(fā)一些返回bool值的Function。本質(zhì)上也是一個(gè)單元函數(shù)接口,其抽象方法test接受一個(gè)泛型參數(shù)T,返回一個(gè)boolean值。等價(jià)于一個(gè)Function的boolean型返回值的子集。

image

其默認(rèn)方法也封裝了and、or和negate邏輯。寫個(gè)小例子看看:

image

Predicate在Stream中有應(yīng)用,Stream的filter方法就是接受Predicate作為入?yún)⒌?。這個(gè)具體在后面使用Stream的時(shí)候再分析深入。

其他Predicate接口:

BiPredicate:boolean test(T t, U u);接受兩個(gè)參數(shù)的二元謂詞

DoublePredicate:boolean test(double value);入?yún)閐ouble的謂詞函數(shù)

IntPredicate:boolean test(int value);入?yún)閕nt的謂詞函數(shù)

LongPredicate:boolean test(long value);入?yún)閘ong的謂詞函數(shù)

Consumer

看名字就可以想到,這像謂詞函數(shù)接口一樣,也是一個(gè)Function接口的特殊表達(dá)——接受一個(gè)泛型參數(shù),不需要返回值的函數(shù)接口。

image

這個(gè)接口聲明太重要了,對(duì)于一些純粹consume型的函數(shù),沒有Consumer的定義真無法被Function家族的函數(shù)接口表達(dá)。因?yàn)镕unction一定需要一個(gè)泛型參數(shù)作為返回值類型(當(dāng)然不排除你使用Function來定義,但是一直返回一個(gè)無用的值)。比如下面的例子,如果沒有Consumer,類似的行為使用Function表達(dá)就一定需要一個(gè)返回值。

image

其他Consumer接口:

BiConsumer:void accept(T t, U u);接受兩個(gè)參數(shù)

DoubleConsumer:void accept(double value);接受一個(gè)double參數(shù)

IntConsumer:void accept(int value);接受一個(gè)int參數(shù)

LongConsumer:void accept(long value);接受一個(gè)long參數(shù)

ObjDoubleConsumer:void accept(T t, double value);接受一個(gè)泛型參數(shù)一個(gè)double參數(shù)

ObjIntConsumer:void accept(T t, int value);接受一個(gè)泛型參數(shù)一個(gè)int參數(shù)

ObjLongConsumer:void accept(T t, long value);接受一個(gè)泛型參數(shù)一個(gè)long參數(shù)

Supplier

最后說的是一個(gè)叫做Supplier的函數(shù)接口,其聲明如下:

image

其簡潔的聲明,會(huì)讓人以為不是函數(shù)。這個(gè)抽象方法的聲明,同Consumer相反,是一個(gè)只聲明了返回值,不需要參數(shù)的函數(shù)(這還叫函數(shù)?)。也就是說Supplier其實(shí)表達(dá)的不是從一個(gè)參數(shù)空間到結(jié)果空間的映射能力,而是表達(dá)一種生成能力,因?yàn)槲覀兂R姷膱?chǎng)景中不止是要consume(Consumer)或者是簡單的map(Function),還包括了new這個(gè)動(dòng)作。而Supplier就表達(dá)了這種能力。

比如你要是返回一個(gè)常量,那可以使用類似的做法:

這保證supplier對(duì)象輸出的一直是1。

如果是要利用構(gòu)造函數(shù)的能力呢?就可以這樣:

image

這樣的輸出可以看到,全部的對(duì)象都是new出來的。

這樣的場(chǎng)景在Stream計(jì)算中會(huì)經(jīng)常用到,具體在分析Java 8中Stream的時(shí)候再深入。

其他Supplier接口:

BooleanSupplier:boolean getAsBoolean();返回boolean

DoubleSupplier:double getAsDouble();返回double

IntSupplier:int getAsInt();返回int

LongSupplier:long getAsLong();返回long

總結(jié)

整個(gè)函數(shù)式接口的大概總結(jié)如下:

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

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

  • 時(shí)光匆匆,時(shí)間轉(zhuǎn)瞬即逝。 人在某些時(shí)候,總會(huì)懷念過去,他們也不知道這其中的好壞是非,但是莫名的就是會(huì)回憶起以前...
    53347439c7a1閱讀 587評(píng)論 0 1
  • 第46章 水是平的嗎 我感到喝極了。臆想之中,周圍都是大海,波濤洶涌。 睜開眼,我躺在李老師的懷中。 “琳琳,你怎...
    華芳國閱讀 145評(píng)論 0 0
  • oop Examples of GoF Design Patterns in Java's core libra...
    holysu閱讀 1,163評(píng)論 0 2
  • 事件1:早上沒跟著鬧鐘起來,不過醒來后就開始學(xué)習(xí)了,因?yàn)闆]去公司上班,從開始學(xué)習(xí)后就很有狀態(tài),效果挺滿意的 內(nèi)心狀...
    一閃一閃亮晶晶呀閱讀 94評(píng)論 0 0

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