最近看了慕課網(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ù)接口有三條重要法則:
- 一個(gè)函數(shù)接口只有一個(gè)抽象方法。
- 在 Object 類中屬于公共方法的抽象方法不會(huì)被視為單一抽象方法。
函數(shù)接口可以有默認(rèn)方法和靜態(tài)方法。 - 任何滿足單一抽象方法法則的接口,都會(huì)被自動(dòng)視為函數(shù)接口。這包括 Runnable 和 Callable 等傳統(tǒng)的接口,以及您自己構(gòu)建的自定義接口。
怎么理解,看幾個(gè)例子。
比如:你聲明一個(gè)接口:

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

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

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

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

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

這又不行了,因?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:

函數(shù)式接口Func:

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

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

上面例子列舉了一個(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ù)為

函數(shù)意為將參數(shù)T傳遞給一個(gè)函數(shù),返回R。即
其默認(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ù):

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

其輸出結(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)單元算子和二元算子。
算子的接口聲明如下:

二元算子的聲明:

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

例子里補(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型返回值的子集。

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

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ù)接口。

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

其他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ù)接口,其聲明如下:

其簡潔的聲明,會(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ù)的能力呢?就可以這樣:

這樣的輸出可以看到,全部的對(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é)如下:
