還不懂 Consumer 接口?那你就OUT了
背景
沒(méi)錯(cuò),我還在做 XXXX 項(xiàng)目,還在與第三方對(duì)接接口,不同的是這次是對(duì)自己業(yè)務(wù)邏輯的處理。
在開(kāi)發(fā)過(guò)程中我遇到這么一個(gè)問(wèn)題:
表結(jié)構(gòu):一張主表A ,一張關(guān)聯(lián)表B ,表 A 中存儲(chǔ)著表 B 記錄的狀態(tài)。
場(chǎng)景:第一步創(chuàng)建主表數(shù)據(jù),插入A表;第二步調(diào)用第三方接口插入B表同時(shí)更新A表的狀態(tài)。此時(shí)大家應(yīng)該都會(huì)想到在進(jìn)行第二步的時(shí)候需要做好數(shù)據(jù)的冪等性。這樣的話(huà)就會(huì)存在以下幾種情況:
一、B表中不存在與A表關(guān)聯(lián)的數(shù)據(jù),此時(shí)需要調(diào)用第三方接口,插入B表同時(shí)更新A表的狀態(tài);
二、B表中存在與A表關(guān)聯(lián)的數(shù)據(jù);
A表中的狀態(tài)為處理中:直接返回處理中字樣; A表中的狀態(tài)為處理成功:直接返回成功的字樣; A表中的狀態(tài)為處理失?。捍藭r(shí)需要調(diào)用第三方接口,更新B表同時(shí)更新A表的狀態(tài);
代碼實(shí)現(xiàn)
首先我是這樣編寫(xiě)的偽代碼
B?b?=?this.baseMapper.selectOne(queryWrapper);
if?(b?!=?null)?{
?String?status?=?b.getStatus();
?if?(Objects.equals(Constants.STATUS_ING,?status)){
??return?"處理中";
?}?else?if?(Objects.equals(Constants.STATUS_SUCCESS,?status)){
??return?"處理成功";
?}
?//失敗的操作
?//請(qǐng)求第三方接口并解析響應(yīng)結(jié)果
?......
?if?(ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode()))?{
????????......
????????//更新B表操作
??bb.setStatus(Constants.STATUS_ING);
??mapper.updateById(bb);
??//更新A表的狀態(tài)
??a.setStatus(Constants.STATUS_ING);
??aMapper.updateById(a);
?}
?
}?else?{
?//請(qǐng)求第三方接口并解析響應(yīng)結(jié)果
?......
?if?(ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode()))?{
????????......
????????//插入B表操作
??bb.setStatus(Constants.STATUS_ING);
??mapper.insert(bb);
??//更新A表的狀態(tài)
??a.setStatus(Constants.STATUS_ING);
??aMapper.updateById(a);
?}
}
不知道細(xì)心的小伙伴是否發(fā)現(xiàn),存在B表記錄并且狀態(tài)為“失敗”的情況和不存在B表的情況除了插入B表或者更新B表的操作之外,其余的操作都是相同的。
如果我們想要將公共的部分抽取出來(lái),發(fā)現(xiàn)都比較零散,還不如不抽取,但是不抽取代碼又存在大量重復(fù)的代碼不符合我的風(fēng)格。于是我便將手伸向了 Consumer 接口。
更改之后的偽代碼
B?b?=?this.baseMapper.selectOne(queryWrapper);
if?(b?!=?null)?{
?String?status?=?b.getStatus();
?if?(Objects.equals(Constants.STATUS_ING,?status)){
??return?"處理中";
?}?else?if?(Objects.equals(Constants.STATUS_SUCCESS,?status)){
??return?"處理成功";
?}
?//失敗的操作
?getResponse(dto,?response,?s?->?mapper.updateById(s));
}?else?{
?getResponse(dto,?response,?s?->?mapper.updateById(s));
}
public?void?getResponse(DTO?dto,?Response?response,?Consumer?consumer){
?//請(qǐng)求第三方接口并解析響應(yīng)結(jié)果
?......
?if?(ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode()))?{
????????......
??bb.setStatus(Constants.STATUS_ING);
?
??consumer.accept(bb);
??//更新A表的狀態(tài)
??a.setStatus(Constants.STATUS_ING);
??aMapper.updateById(a);
?}
}
看到這,如果大家都已經(jīng)看懂了,那么恭喜你,說(shuō)明你對(duì) Consumer 的使用已經(jīng)全部掌握了。如果你還存在一絲絲的疑慮,那么就接著往下看,我們將介紹一下四種常見(jiàn)的函數(shù)式接口。

函數(shù)式接口
那什么是函數(shù)式接口呢?函數(shù)式接口是只有一個(gè)抽象方法(Object的方法除外),但是可以有多個(gè)非抽象方法的接口,它表達(dá)的是一種邏輯上的單一功能。
@FunctionalInterface
@FunctionalInterface ?注解用來(lái)表示該接口是函數(shù)式接口。它有助于及早發(fā)現(xiàn)函數(shù)式接口中出現(xiàn)的或接口繼承的不適當(dāng)?shù)姆椒暶鳌?/p>
如果接口用該注解來(lái)注釋?zhuān)珜?shí)際上不是函數(shù)式接口,則會(huì)在編譯時(shí)報(bào)錯(cuò)。
Consumer
我們一般稱(chēng)之為“消費(fèi)者”,它表示接受單個(gè)輸入?yún)?shù)但不返回結(jié)果的操作。不同于其它函數(shù)式接口,Consumer 預(yù)期通過(guò)副作用進(jìn)行操作。
那什么又是副作用呢?說(shuō)一下我所理解的副作用,副作用其實(shí)就是一個(gè)函數(shù)是否會(huì)修改它范圍之外的資源,如果有就叫有副作用,反之為沒(méi)有副作用。比如修改全局變量,修改輸入?yún)?shù)所引用的對(duì)象等。
@FunctionalInterface
public?interface?Consumer<T>?{
????/**
?????*??對(duì)給定的參數(shù)執(zhí)行此操作。
?????*/
????void?accept(T?t);
????/**
?????*?
?????*??返回一個(gè)組合的 Consumer ,依次執(zhí)行此操作,然后執(zhí)行after操作。
?????*??如果執(zhí)行任一操作會(huì)拋出異常,它將被轉(zhuǎn)發(fā)到組合操作的調(diào)用者。
?????*??如果執(zhí)行此操作會(huì)引發(fā)異常,則不會(huì)執(zhí)行after操作。
?????*/
????default?Consumer?andThen(Consumer?super?T>?after)? {
????????Objects.requireNonNull(after);
????????return?(T?t)?->?{?accept(t);?after.accept(t);?};
????}
}
正如我們案例中遇到的場(chǎng)景,我們只需要將要執(zhí)行的邏輯方法當(dāng)作參數(shù)傳入 getResponse() 中,然后在該方法中執(zhí)行 accept() 方法進(jìn)行消費(fèi)即可。如果還不理解,我們可以把它轉(zhuǎn)換為匿名內(nèi)部類(lèi)的調(diào)用方式。
?getResponse(dto,?response,?new?Consumer()?{
????@Override
????public?void?accept(B?bb)?{
??????mapper.insert(bb);
????}
});
當(dāng)調(diào)用accept() 方法的時(shí)候就會(huì)去調(diào)用匿名內(nèi)部類(lèi)的方法了,也就是我們傳入 getResponse() 的邏輯方法。
Supplier
我們一般稱(chēng)之為“生產(chǎn)者”,沒(méi)有參數(shù)輸入,但是能返回結(jié)果,為結(jié)果的提供者。
@FunctionalInterface
public?interface?Supplier<T>?{
????/**
?????*??獲取一個(gè)結(jié)果
?????*/
????T?get();
}
可以舉個(gè)簡(jiǎn)單的例子感受下:
Optional?optional?=?Optional.empty();
optional.orElseGet(()->Math.random()?);
//orElseGet?方法的源碼,里邊用到了?get?方法
public?T?orElseGet(Supplier?extends?T>?other)?{???
????return?value?!=?null???value?:?other.get();
}
Function
我把它稱(chēng)為“轉(zhuǎn)換者”,表示接收一個(gè)參數(shù)通過(guò)處理之后返回一個(gè)結(jié)果的函數(shù)。
@FunctionalInterface
public?interface?Function<T,?R>?{
????/**
?????*??將?T?類(lèi)型的參數(shù)傳入,經(jīng)過(guò)函數(shù)表達(dá)式的計(jì)算,返回?R?類(lèi)型的結(jié)果
?????*/
????R?apply(T?t);
????/**
?????*?返回一個(gè)組合函數(shù),先將參數(shù)應(yīng)用于 before 函數(shù),然后將結(jié)果應(yīng)用于當(dāng)前函數(shù),返回最終結(jié)果。
?????*?如果對(duì)任一函數(shù)的求值引發(fā)異常,則會(huì)將其轉(zhuǎn)發(fā)給組合函數(shù)的調(diào)用方。
?????*/
????default??Function?compose(Function?super?V,???extends?T>?before)? {
????????Objects.requireNonNull(before);
????????return?(V?v)?->?apply(before.apply(v));
????}
????/**
?????*?返回一個(gè)組合函數(shù),先將參數(shù)應(yīng)用與當(dāng)前函數(shù),然后將結(jié)果應(yīng)用于 after 函數(shù),返回最終的結(jié)果。
?????*?如果對(duì)任一函數(shù)的求值引發(fā)異常,則會(huì)將其轉(zhuǎn)發(fā)給組合函數(shù)的調(diào)用方。?
?????*/
????default??Function?andThen(Function?super?R,???extends?V>?after)? {
????????Objects.requireNonNull(after);
????????return?(T?t)?->?after.apply(apply(t));
????}
????/**
?????*??返回始終返回其輸入?yún)?shù)的函數(shù)。
?????*/
????static??Function?identity()? {
????????return?t?->?t;
????}
}
我們?cè)?lambda 表達(dá)式中應(yīng)用比較多,所以我們來(lái)簡(jiǎn)單演示下:
@Data
@AllArgsConstructor
public?class?Teacher?{
????private?String?name;
????private?int?age;
}
public?class?TeacherTest?{
????public?static?void?main(String[]?args)?{
???????List?list?=?Arrays.asList(
????????????new?Teacher("張三",25),
????????????new?Teacher("李四",28),
????????????new?Teacher("王五",18));
??????List?collect?=?list.stream().map(item?->?item.getName()).collect(Collectors.toList());
??????System.out.println(collect);
????}
}
其中 map 接收的參數(shù)就是 Function 類(lèi)型, item 為傳入?yún)?shù),item.getName() ?為返回處理的結(jié)果,最后輸出結(jié)果為
[張三,?李四,?王五]
Predicate
我們稱(chēng)之為“判斷者”,通過(guò)接收參數(shù) T 來(lái)返回 boolean 的結(jié)果。
@FunctionalInterface
public?interface?Predicate<T>?{
????/**
?????*??接收一個(gè)參數(shù),?判斷這個(gè)參數(shù)是否匹配某種規(guī)則,?匹配成功返回true,?匹配失敗則返回false
?????*/
????boolean?test(T?t);
????/**
?????*??接收一個(gè)?Predicate?類(lèi)型的參數(shù),用當(dāng)前函數(shù)和?other?函數(shù)邏輯與判斷參數(shù)?t?是否匹配規(guī)則,成功返回true,失敗返回?false?
?????*??如果當(dāng)前函數(shù)返回?false,則?other?函數(shù)不進(jìn)行計(jì)算
?????*?在評(píng)估?Predicate?期間引發(fā)的任何異常都會(huì)轉(zhuǎn)發(fā)給調(diào)用方
?????*/
????default?Predicate?and(Predicate?super?T>?other)? {
????????Objects.requireNonNull(other);
????????return?(t)?->?test(t)?&&?other.test(t);
????}
????/**
?????*??返回當(dāng)前Predicate取反操作之后的Predicate
?????*/
????default?Predicate?negate()? {
????????return?(t)?->?!test(t);
????}
????/**
?????*??接收一個(gè)?Predicate?類(lèi)型的參數(shù),用當(dāng)前函數(shù)和?other?函數(shù)?邏輯或?判斷參數(shù)?t?是否匹配規(guī)則,成功返回true,失敗返回?false?
?????*??如果當(dāng)前函數(shù)返回?true,則?other?函數(shù)不進(jìn)行計(jì)算
?????*?在評(píng)估?Predicate?期間引發(fā)的任何異常都會(huì)轉(zhuǎn)發(fā)給調(diào)用方
?????*/
????default?Predicate?or(Predicate?super?T>?other)? {
????????Objects.requireNonNull(other);
????????return?(t)?->?test(t)?||?other.test(t);
????}
????/**
?????*??靜態(tài)方法:傳入一個(gè)參數(shù),用來(lái)生成一個(gè) Predicate,調(diào)用test()?方法時(shí)調(diào)的 object -> targetRef.equals(object)?函數(shù)式
?????*
?????*/
????static??Predicate?isEqual(Object?targetRef)? {
????????return?(null?==?targetRef)
??????????????????Objects::isNull
????????????????:?object?->?targetRef.equals(object);
????}
}
相信大家在編碼過(guò)程中經(jīng)常會(huì)遇到該函數(shù)式接口,我們舉個(gè)例子來(lái)說(shuō)一下:
public?static?void?main(String[]?args)?{
????List?list?=?Arrays.asList(
????????new?Teacher("張三",25),
????????new?Teacher("李四",28),
????????new?Teacher("王五",18));
????list?=?list.stream().filter(item?->?item.getAge()>25).collect(Collectors.toList());
????list.stream().forEach(item->System.out.println(item.getName()));
}
其中 filter() 的參數(shù)為 Predicate 類(lèi)型的,返回結(jié)果為:李四
看到這兒,我們常見(jiàn)的四種函數(shù)式接口就已經(jīng)介紹完了。說(shuō)實(shí)話(huà),函數(shù)式接口我已經(jīng)看過(guò)好幾遍了,尤其是 Consumer 和 Supplier。當(dāng)時(shí)只是腦子里學(xué)會(huì)了,沒(méi)有應(yīng)用到具體的項(xiàng)目中,下次再遇到的時(shí)候還是一臉懵逼,不知道大家有沒(méi)有這種感受。
所以我們需要總結(jié)經(jīng)驗(yàn)教訓(xùn),一定要將代碼的原理搞懂,然后多敲幾遍,爭(zhēng)取應(yīng)用到自己的項(xiàng)目中,提升自己的編碼能力。
以上就是今天的全部?jī)?nèi)容了,如果你有不同的意見(jiàn)或者更好的idea,歡迎聯(lián)系阿Q,添加阿Q可以加入技術(shù)交流群參與討論呦!
文章風(fēng)格多變,配圖通俗易懂,故事生動(dòng)有趣。推薦幾篇文章,何不試著讀讀呢?
推薦閱讀:
Redis 宕機(jī),數(shù)據(jù)丟了,老板要辭退我
程序員必備基礎(chǔ):10種常見(jiàn)安全漏洞淺析
網(wǎng)關(guān)技術(shù)選型,為什么選擇 Openresty ?
Redis主節(jié)點(diǎn)的Key已過(guò)期的處理
歡迎關(guān)注微信公眾號(hào):互聯(lián)網(wǎng)全棧架構(gòu),收取更多有價(jià)值的信息。
