Java8新特性探索之函數(shù)式接口
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質文章,第一時間送達
? 作者?|??拼搏吧,少年
來源 |? urlify.cn/vQry6r
一、為什么引入函數(shù)式接口
作為Java函數(shù)式編程愛好者,我們都知道方法引用和 Lambda 表達式都必須被賦值,同時賦值需要類型信息才能使編譯器保證類型的正確性。
我們先看一個Lambda代碼示例:
x?->?x.toString()
我們清楚這里返回類型必須是?String,但?x?是什么類型呢?
Lambda 表達式包含類型推導(編譯器會自動推導出類型信息,避免了程序員顯式地聲明),編譯器必須能夠以某種方式推導出?x?的類型以生成正確的代碼。
同樣方法引用也存在此問題,假設你要傳遞?System.out :: println?到你正在編寫的方法 ,你怎么知道傳遞給方法的參數(shù)的類型?
為了解決上述問題,Java 8 引入了函數(shù)式接口,在?java.util.function?包,它包含一組接口,這些接口是 Lambda 表達式和方法引用的目標類型,每個接口只包含一個抽象方法,稱為函數(shù)式方法。只有確保接口中有且僅有一個抽象方法,Lambda表達式的類型信息才能順利地進行推導。
二、如何使用函數(shù)式接口
在編寫接口時,可以使用?@FunctionalInterface?注解強制執(zhí)行此函數(shù)式方法模式:
在接口上使用注解?
@FunctionalInterface?,一旦使用該注解來定義接口,編譯器將會強制檢查該接口是否確實有且僅有一個抽象方法,否則將會報錯。@FunctionalInterface
public?interface?MyFunction?{
?/**
??*?自定義的抽象方法
??*/
?void?run();
}在函數(shù)式接口,有且僅有一個抽象方法,
Object的public方法除外@FunctionalInterface
public?interface?MyFunction?{
?
?/**
??*?自定義的抽象方法
??*/
?void?run();
?
?/**
??*?Object的equals方法
??*?@param?obj
??*?@return
??*/
?@Override
?boolean?equals(Object?obj);
?
?/**
??*?Object的toString方法
??*?@return
??*/
?@Override
?String?toString();
?
?/**
??*?Object的hashCode方法
??*?@return
??*/
?@Override
?int?hashCode();
?
}在函數(shù)式接口中,我們可以使用
default修飾符定義默認方法,使用static修飾符定義靜態(tài)方法@FunctionalInterface
public?interface?MyFunction?{
?
?/**
??*?自定義的抽象方法
??*/
?void?run();
?
?/**
??*?static修飾符定義靜態(tài)方法
??*/
????static?void?staticRun()?{
????????System.out.println("接口中的靜態(tài)方法");
????}
?
????/**
?????*?default修飾符定義默認方法
?????*/
????default?void?defaultRun()?{
????????System.out.println("接口中的默認方法");
????}
????
}
為大家演示下自定義無泛型的函數(shù)式接口測試實例:
/**
*?自定義的無泛型函數(shù)式接口
*/
@FunctionalInterface
public?interface?MyFunction?{
?
?/**
??*?自定義的抽象方法
??*?@param?x
??*/
?void?run(Integer?x);
?
????/**
?????*?default修飾符定義默認方法
?????*?@param?x
?????*/
????default?void?defaultMethod(Integer?x)?{
????????System.out.println("接口中的默認方法,接收參數(shù)是:"?+?x);
????}
????
}
/**
*?測試類
*/
public?class?MyFunctionTest?{
?@Test
?public?void?functionTest()?{
??test(6,?(x)?->?System.out.println("接口中的抽象run方法,接收參數(shù)是:"?+?x));
?}
?
?public?void?test(int?n,?MyFunction?function)?{
??System.out.println(n);
??function.defaultMethod(n);
??function.run(n);
?}
?
}輸出結果:
6
接口中的默認方法,接收參數(shù)是:6
接口中的抽象run方法,接收參數(shù)是:6為大家演示下自定義有泛型的函數(shù)式接口測試實例:
/**
?*?自定義的有泛型函數(shù)式接口
?*/
@FunctionalInterface
public?interface?MyFunctionGeneric?{
?/**
??*?轉換值
??*?@param?t
??*?@return
??*/
?T?convertValue(T?t);
?
}
/**
*?測試類
*/
public?class?MyFunctionGenericTest?{
?@Test
?public?void?convertValueTest()?{
??String?result?=?toLowerCase((x)?->?x.toLowerCase(),?"ABC");
??System.out.println(result);
?}
?
?public?String?toLowerCase(MyFunctionGeneric?functionGeneric,?String?value)?{
??return?functionGeneric.convertValue(value);
?}
?
}輸出結果:
abc
注意:作為參數(shù)傳遞 Lambda 表達式:為了將 Lambda 表達式作為參數(shù)傳遞,接收Lambda 表達式的參數(shù)類型必須是與該 Lambda 表達式兼容的函數(shù)式接口 的類型。
三、Java8四大內置核心函數(shù)式接口
首先總覽下四大函數(shù)式接口的特點說明:
| 接口 | 參數(shù)類型 | 返回類型 | 方法 | 說明 |
|---|---|---|---|---|
| Consumer | T | void | void accept(T t) | 消費型接口,對類型T參數(shù)操作,無返回結果 |
| Supplier | - | T | T get() | 供給型接口,創(chuàng)造T類型參數(shù) |
| Function | T | R | R apply(T t) | 函數(shù)型接口,對類型T參數(shù)操作,返回R類型參數(shù) |
| Predicate | T | boolean | boolean test(T t) | 斷言型接口,對類型T進行條件篩選操作 |
消費型接口
Consumer
java.util.function.Consumer?接口是消費一個數(shù)據(jù),其數(shù)據(jù)類型由泛型決定。
接口源碼:
package?java.util.function;
import?java.util.Objects;
@FunctionalInterface
public?interface?Consumer?{
????void?accept(T?t);
????default?Consumer?andThen(Consumer?super?T>?after)?{
????????Objects.requireNonNull(after);
????????return?(T?t)?->?{?accept(t);?after.accept(t);?};
????}
}
抽象方法: void accept(T t),接收并消費一個指定泛型的數(shù)據(jù),無需返回結果。默認方法: default Consumer,如果一個方法的參數(shù)和返回值全都是 Consumer 類型,那么就可以實現(xiàn)效果:消費數(shù)據(jù)的時候,首先做一個操作,然后再做一個操作,實現(xiàn)組合andThen(Consumer super T> after)
public?class?ConsumerTest?{
?/**
??*?先計算總分,再計算平均分
??*/
?@Test
?public?void?calculate()?{
??Integer[]?fraction?=?new?Integer[]?{?65,?76,?85,?92,?88,?99?};
??consumer(fraction,?x?->?System.out.println(Arrays.stream(x).mapToInt(Integer::intValue).sum()),
????y?->?System.out.println(Arrays.stream(y).mapToInt(Integer::intValue).average().getAsDouble()));
?}
?
?public?void?consumer(Integer[]?fraction,?Consumer?x,?Consumer?y)?{
??x.andThen(y).accept(fraction);
?}
?
}
輸出結果:
505
84.16666666666667
由于Consumer的default方法所帶來的嵌套調用(連鎖調用),對行為的抽象的函數(shù)式編程理念,展示的淋漓盡致。
其他的消費型函數(shù)式接口匯總說明:
| 接口名稱 | 方法名稱 | 方法簽名 |
|---|---|---|
| DoubleConsumer | accept | (double) -> void |
| IntConsumer | accept | (int) -> void |
| LongConsumer | accept | (long) -> void |
| ObjDoubleConsumer | accept | (T, double) -> void |
| ObjIntConsumer | accept | (T, int) -> void |
| ObjLongConsumer | accept | (T, long) -> void |
供給型接口
Supplier
java.util.function.Supplier?接口僅包含一個無參的方法:?T get()?,用來獲取一個泛型參數(shù)指定類型的對象數(shù)據(jù)。
接口源碼:
package?java.util.function;
@FunctionalInterface
public?interface?Supplier?{
????T?get();
}
由于這是一個函數(shù)式接口,意味著對應的Lambda表達式需要對外提供一個符合泛型類型的對象數(shù)據(jù)。
public?class?SupplierTest?{
?public?int?getMax(Supplier?supplier)?{
??return?supplier.get();
?}
?
?/**
??*?獲取數(shù)組元素最大值
??*/
?@Test
?public?void?getMaxTest()?{
??Integer[]?data?=?new?Integer[]?{?5,?4,?6,?3,?2,?1?};
??int?result?=?getMax(()?->?{
???int?max?=?0;
???for?(int?i?=?0;?i?????max?=?Math.max(max,?data[i]);
???}
???return?max;
??});
??System.out.println(result);
?}
?
}
其他的供給型函數(shù)式接口匯總說明:
| 接口名稱 | 方法名稱 | 方法簽名 |
|---|---|---|
| BooleanSupplier | getAsBoolean | () -> boolean |
| DoubleSupplier | getAsDouble | () -> double |
| IntSupplier | getAsInt | () -> int |
| LongSupplier | getAsLong | () -> long |
函數(shù)型接口
Function
java.util.function.Function?接口用來根據(jù)一個類型的數(shù)據(jù)得到另一個類型的數(shù)據(jù),前者稱為前置條件,后者稱為后置條件。
接口源碼:
package?java.util.function;
import?java.util.Objects;
@FunctionalInterface
public?interface?Function?{
????R?apply(T?t);
????default??Function?compose(Function?super?V,???extends?T>?before)?{
????????Objects.requireNonNull(before);
????????return?(V?v)?->?apply(before.apply(v));
????}
????default??Function?andThen(Function?super?R,???extends?V>?after)?{
????????Objects.requireNonNull(after);
????????return?(T?t)?->?after.apply(apply(t));
????}
????static??Function?identity()?{
????????return?t?->?t;
????}
}
抽象方法?
apply(T t):該方法接收入?yún)⑹且粋€泛型T對象,并返回一個泛型T對象。默認方法
andThen(Function super R, ? extends V> after):該方法接受一個行為,并將父方法處理過的結果作為參數(shù)再處理。compose(Function super V, ? extends T> before):該方法正好與andThen相反,它是先自己處理然后將結果作為參數(shù)傳給父方法執(zhí)行。@Test
public?void?andThenAndComposeTest()?{
????//?計算公式相同
????Function?andThen1?=?x?->?x?+?1;
????Function?andThen2?=?x?->?x?*?2;
????Function?compose1?=?y?->?y?+?1;
????Function?compose2?=?y?->?y?*?2;
????//?注意調用的先后順序
????//?傳入?yún)?shù)2后,先執(zhí)行andThen1計算,將結果再傳入andThen2計算
????System.out.println(andThen1.andThen(andThen2).apply(2));
????//?傳入?yún)?shù)2后,先執(zhí)行compose2計算,將結果再傳入compose1計算
????System.out.println(compose1.compose(compose2).apply(2));
}輸出結果:
6
5靜態(tài)方法
identity():獲取到一個輸入?yún)?shù)和返回結果一樣的Function實例。
來一個自駕九寨溝的代碼示例:
public?class?FunctionTest?{
?
?@Test
?public?void?findByFunctionTest()?{
??Function?getMoney?=?m?->?m.add(new?BigDecimal(1000));
??BigDecimal?totalCost?=?getMoney.apply(new?BigDecimal(500));
??System.out.println("張三的錢包原本只有500元,自駕川西得去銀行再取1000元,取錢后張三錢包總共有"?+???????????Function.identity().apply(totalCost)?+?"元");
??BigDecimal?surplus?=?cost(totalCost,?(m)?->?{
???System.out.println("第二天出發(fā)前發(fā)現(xiàn)油不足,加油前有"?+?m?+?"元");
???BigDecimal?lubricate?=?m.subtract(new?BigDecimal(300));
???System.out.println("加油300后還剩余"?+?lubricate?+?"元");
???return?lubricate;
??},?(m)?->?{
???System.out.println("到達景區(qū)門口,買景區(qū)票前有"?+?m?+?"元");
???BigDecimal?tickets?=?m.subtract(new?BigDecimal(290));
???System.out.println("買景區(qū)票290后還剩余"?+?tickets?+?"元");
???return?tickets;
??});
??System.out.println("最后張三返程到家還剩余"?+?surplus?+?"元");
?}
?public?BigDecimal?cost(BigDecimal?money,?Function?lubricateCost,
???Function?ticketsCost)?{
??Function?firstNight?=?(m)?->?{
???System.out.println("第一晚在成都住宿前有"?+?m?+?"元");
???BigDecimal?first?=?m.subtract(new?BigDecimal(200));
???System.out.println("交完200住宿費還剩余"?+?first?+?"元");
???return?first;
??};
??Function?secondNight?=?(m)?->?{
???System.out.println("第二晚在九寨縣住宿前有"?+?m?+?"元");
???BigDecimal?second?=?m.subtract(new?BigDecimal(200));
???System.out.println("交完200住宿費還剩余"?+?second?+?"元");
???return?second;
??};
??return?lubricateCost.andThen(ticketsCost).andThen(secondNight).compose(firstNight).apply(money);
?}
}
輸出結果:
張三的錢包原本只有500元,自駕川西得去銀行再取1000元,取錢后張三錢包總共有1500元
第一晚在成都住宿前有1500元
交完200住宿費還剩余1300元
第二天出發(fā)前發(fā)現(xiàn)油不足,加油前有1300元
加油300后還剩余1000元
到達景區(qū)門口,買景區(qū)票前有1000元
買景區(qū)票290后還剩余710元
第二晚在九寨縣住宿前有710元
交完200住宿費還剩余510元
最后張三返程到家還剩余510元其他的函數(shù)型函數(shù)式接口匯總說明:
| 接口名稱 | 方法名稱 | 方法簽名 |
|---|---|---|
| BiFunction | apply | (T, U) -> R |
| DoubleFunction | apply | (double) -> R |
| DoubleToIntFunction | applyAsInt | (double) -> int |
| DoubleToLongFunction | applyAsLong | (double) -> long |
| IntFunction | apply | (int) -> R |
| IntToDoubleFunction | applyAsDouble | (int) -> double |
| IntToLongFunction | applyAsLong | (int) -> long |
| LongFunction | apply | (long) -> R |
| LongToDoubleFunction | applyAsDouble | (long) -> double |
| LongToIntFunction | applyAsInt | (long) -> int |
| ToDoubleFunction | applyAsDouble | (T) -> double |
| ToDoubleBiFunction | applyAsDouble | (T, U) -> double |
| ToIntFunction | applyAsInt | (T) -> int |
| ToIntBiFunction | applyAsInt | (T, U) -> int |
| ToLongFunction | applyAsLong | (T) -> long |
| ToLongBiFunction | applyAsLong | (T, U) -> long |
斷言型接口
Predicate
java.util.function.Predicate?接口中包含一個抽象方法:?boolean test(T t)?,用于條件判斷的場景。默認方法:and or nagte?(取反)。
接口源碼:
package?java.util.function;
import?java.util.Objects;
@FunctionalInterface
public?interface?Predicate?{
????boolean?test(T?t);
????default?Predicate?and(Predicate?super?T>?other)?{
????????Objects.requireNonNull(other);
????????return?(t)?->?test(t)?&&?other.test(t);
????}
????default?Predicate?negate()?{
????????return?(t)?->?!test(t);
????}
????default?Predicate?or(Predicate?super?T>?other)?{
????????Objects.requireNonNull(other);
????????return?(t)?->?test(t)?||?other.test(t);
????}
????static??Predicate?isEqual(Object?targetRef)?{
????????return?(null?==?targetRef)
??????????????????Objects::isNull
????????????????:?object?->?targetRef.equals(object);
????}
}
既然是條件判斷,就會存在與、或、非三種常見的邏輯關系。其中將兩個?Predicate?條件使用與邏輯連接起來實現(xiàn)并且的效果時,類始于?Consumer接口?andThen()函數(shù) 其他三個雷同。
public?class?PredicateTest?{
?/**
??*?查找在渝北的Jack
??*/
?@Test
?public?void?findByPredicateTest()?{
??List?list?=?Lists.newArrayList(new?User("Johnson",?"渝北"),?new?User("Tom",?"渝中"),?new?User("Jack",?"渝北"));
??getNameAndAddress(list,?(x)?->?x.getAddress().equals("渝北"),?(x)?->?x.getName().equals("Jack"));
?}
?
?public?void?getNameAndAddress(List?users,?Predicate?name,?Predicate?address)?{
??users.stream().filter(user?->?name.and(address).test(user)).forEach(user?->?System.out.println(user.toString()));
?}
}
輸出結果:
User?[name=Jack,?address=渝北]
其他的斷言型函數(shù)式接口匯總說明:
| 接口名稱 | 方法名稱 | 方法簽名 |
|---|---|---|
| BiPredicate | test | (T, U) -> boolean |
| DoublePredicate | test | (double) -> boolean |
| IntPredicate | test | (int) -> boolean |
| LongPredicate | test | (long) -> boolean |
四、總結
Lambda 表達式和方法引用并沒有將 Java 轉換成函數(shù)式語言,而是提供了對函數(shù)式編程的支持。這對 Java 來說是一個巨大的改進,因為這允許你編寫更簡潔明了,易于理解的代碼。
粉絲福利:實戰(zhàn)springboot+CAS單點登錄系統(tǒng)視頻教程免費領取
???
?長按上方微信二維碼?2 秒 即可獲取資料
感謝點贊支持下哈?
