如果還不懂如何使用 Consumer 接口,來青島我當(dāng)面給你講!
背景
沒錯,我還在做 XXXX 項(xiàng)目,還在與第三方對接接口,不同的是這次是對自己業(yè)務(wù)邏輯的處理。
在開發(fā)過程中我遇到這么一個問題:
表結(jié)構(gòu):一張主表A ,一張關(guān)聯(lián)表B ,表 A 中存儲著表 B 記錄的狀態(tài)。
場景:第一步創(chuàng)建主表數(shù)據(jù),插入A表;第二步調(diào)用第三方接口插入B表同時更新A表的狀態(tài)。此時大家應(yīng)該都會想到在進(jìn)行第二步的時候需要做好數(shù)據(jù)的冪等性。這樣的話就會存在以下幾種情況:
一、B表中不存在與A表關(guān)聯(lián)的數(shù)據(jù),此時需要調(diào)用第三方接口,插入B表同時更新A表的狀態(tài);
二、B表中存在與A表關(guān)聯(lián)的數(shù)據(jù);
A表中的狀態(tài)為處理中:直接返回處理中字樣;
A表中的狀態(tài)為處理成功:直接返回成功的字樣;
A表中的狀態(tài)為處理失?。捍藭r需要調(diào)用第三方接口,更新B表同時更新A表的狀態(tài);
代碼實(shí)現(xiàn)
首先我是這樣編寫的偽代碼
- 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 "處理成功";
- }
- //失敗的操作
- //請求第三方接口并解析響應(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 {
- //請求第三方接口并解析響應(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表的操作之外,其余的操作都是相同的。
如果我們想要將公共的部分抽取出來,發(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<B> consumer){
- //請求第三方接口并解析響應(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)看懂了,那么恭喜你,說明你對 Consumer 的使用已經(jīng)全部掌握了。如果你還存在一絲絲的疑慮,那么就接著往下看,我們將介紹一下四種常見的函數(shù)式接口。
函數(shù)式接口
那什么是函數(shù)式接口呢?函數(shù)式接口是只有一個抽象方法(Object的方法除外),但是可以有多個非抽象方法的接口,它表達(dá)的是一種邏輯上的單一功能。
@FunctionalInterface
@FunctionalInterface 注解用來表示該接口是函數(shù)式接口。它有助于及早發(fā)現(xiàn)函數(shù)式接口中出現(xiàn)的或接口繼承的不適當(dāng)?shù)姆椒暶鳌?/p>
如果接口用該注解來注釋,但實(shí)際上不是函數(shù)式接口,則會在編譯時報(bào)錯。
Consumer
我們一般稱之為“消費(fèi)者”,它表示接受單個輸入?yún)?shù)但不返回結(jié)果的操作。不同于其它函數(shù)式接口,Consumer 預(yù)期通過副作用進(jìn)行操作。
那什么又是副作用呢?說一下我所理解的副作用,副作用其實(shí)就是一個函數(shù)是否會修改它范圍之外的資源,如果有就叫有副作用,反之為沒有副作用。比如修改全局變量,修改輸入?yún)?shù)所引用的對象等。
- @FunctionalInterface
- public interface Consumer<T> {
- /**
- * 對給定的參數(shù)執(zhí)行此操作。
- */
- void accept(T t);
- /**
- *
- * 返回一個組合的 Consumer ,依次執(zhí)行此操作,然后執(zhí)行after操作。
- * 如果執(zhí)行任一操作會拋出異常,它將被轉(zhuǎn)發(fā)到組合操作的調(diào)用者。
- * 如果執(zhí)行此操作會引發(fā)異常,則不會執(zhí)行after操作。
- */
- default Consumer<T> andThen(Consumer<? super T> after) {
- Objects.requireNonNull(after);
- return (T t) -> { accept(t); after.accept(t); };
- }
- }
正如我們案例中遇到的場景,我們只需要將要執(zhí)行的邏輯方法當(dāng)作參數(shù)傳入 getResponse() 中,然后在該方法中執(zhí)行 accept() 方法進(jìn)行消費(fèi)即可。如果還不理解,我們可以把它轉(zhuǎn)換為匿名內(nèi)部類的調(diào)用方式。
- getResponse(dto, response, new Consumer<B>() {
- @Override
- public void accept(B bb) {
- mapper.insert(bb);
- }
- );
當(dāng)調(diào)用accept() 方法的時候就會去調(diào)用匿名內(nèi)部類的方法了,也就是我們傳入 getResponse() 的邏輯方法。
Supplier
我們一般稱之為“生產(chǎn)者”,沒有參數(shù)輸入,但是能返回結(jié)果,為結(jié)果的提供者。
- @FunctionalInterface
- public interface Supplier<T> {
- /**
- * 獲取一個結(jié)果
- */
- T get();
- }
可以舉個簡單的例子感受下:
- Optional<Double> optional = Optional.empty();
- optional.orElseGet(()->Math.random() );
- //orElseGet 方法的源碼,里邊用到了 get 方法
- public T orElseGet(Supplier<? extends T> other) {
- return value != null ? value : other.get();
- }
Function
我把它稱為“轉(zhuǎn)換者”,表示接收一個參數(shù)通過處理之后返回一個結(jié)果的函數(shù)。
- @FunctionalInterface
- public interface Function<T, R> {
- /**
- * 將 T 類型的參數(shù)傳入,經(jīng)過函數(shù)表達(dá)式的計(jì)算,返回 R 類型的結(jié)果
- */
- R apply(T t);
- /**
- * 返回一個組合函數(shù),先將參數(shù)應(yīng)用于 before 函數(shù),然后將結(jié)果應(yīng)用于當(dāng)前函數(shù),返回最終結(jié)果。
- * 如果對任一函數(shù)的求值引發(fā)異常,則會將其轉(zhuǎn)發(fā)給組合函數(shù)的調(diào)用方。
- */
- default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
- Objects.requireNonNull(before);
- return (V v) -> apply(before.apply(v));
- }
- /**
- * 返回一個組合函數(shù),先將參數(shù)應(yīng)用與當(dāng)前函數(shù),然后將結(jié)果應(yīng)用于 after 函數(shù),返回最終的結(jié)果。
- * 如果對任一函數(shù)的求值引發(fā)異常,則會將其轉(zhuǎn)發(fā)給組合函數(shù)的調(diào)用方。
- */
- default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
- Objects.requireNonNull(after);
- return (T t) -> after.apply(apply(t));
- }
- /**
- * 返回始終返回其輸入?yún)?shù)的函數(shù)。
- */
- static <T> Function<T, T> identity() {
- return t -> t;
- }
- }
我們在 lambda 表達(dá)式中應(yīng)用比較多,所以我們來簡單演示下:
- @Data
- @AllArgsConstructor
- public class Teacher {
- private String name;
- private int age;
- }
- public class TeacherTest {
- public static void main(String[] args) {
- List<Teacher> list = Arrays.asList(
- new Teacher("張三",25),
- new Teacher("李四",28),
- new Teacher("王五",18));
- List<String> collect = list.stream().map(item -> item.getName()).collect(Collectors.toList());
- System.out.println(collect);
- }
- }
其中 map 接收的參數(shù)就是 Function 類型, item 為傳入?yún)?shù),item.getName() 為返回處理的結(jié)果,最后輸出結(jié)果為
- [張三, 李四, 王五]
Predicate
我們稱之為“判斷者”,通過接收參數(shù) T 來返回 boolean 的結(jié)果。
- @FunctionalInterface
- public interface Predicate<T> {
- /**
- * 接收一個參數(shù), 判斷這個參數(shù)是否匹配某種規(guī)則, 匹配成功返回true, 匹配失敗則返回false
- */
- boolean test(T t);
- /**
- * 接收一個 Predicate 類型的參數(shù),用當(dāng)前函數(shù)和 other 函數(shù)邏輯與判斷參數(shù) t 是否匹配規(guī)則,成功返回true,失敗返回 false
- * 如果當(dāng)前函數(shù)返回 false,則 other 函數(shù)不進(jìn)行計(jì)算
- * 在評估 Predicate 期間引發(fā)的任何異常都會轉(zhuǎn)發(fā)給調(diào)用方
- */
- default Predicate<T> and(Predicate<? super T> other) {
- Objects.requireNonNull(other);
- return (t) -> test(t) && other.test(t);
- }
- /**
- * 返回當(dāng)前Predicate取反操作之后的Predicate
- */
- default Predicate<T> negate() {
- return (t) -> !test(t);
- }
- /**
- * 接收一個 Predicate 類型的參數(shù),用當(dāng)前函數(shù)和 other 函數(shù) 邏輯或 判斷參數(shù) t 是否匹配規(guī)則,成功返回true,失敗返回 false
- * 如果當(dāng)前函數(shù)返回 true,則 other 函數(shù)不進(jìn)行計(jì)算
- * 在評估 Predicate 期間引發(fā)的任何異常都會轉(zhuǎn)發(fā)給調(diào)用方
- */
- default Predicate<T> or(Predicate<? super T> other) {
- Objects.requireNonNull(other);
- return (t) -> test(t) || other.test(t);
- }
- /**
- * 靜態(tài)方法:傳入一個參數(shù),用來生成一個 Predicate,調(diào)用test() 方法時調(diào)的 object -> targetRef.equals(object) 函數(shù)式
- *
- */
- static <T> Predicate<T> isEqual(Object targetRef) {
- return (null == targetRef)
- ? Objects::isNull
- : object -> targetRef.equals(object);
- }
- }
相信大家在編碼過程中經(jīng)常會遇到該函數(shù)式接口,我們舉個例子來說一下:
- public static void main(String[] args) {
- List<Teacher> 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 類型的,返回結(jié)果為:李四
看到這兒,我們常見的四種函數(shù)式接口就已經(jīng)介紹完了。說實(shí)話,函數(shù)式接口我已經(jīng)看過好幾遍了,尤其是 Consumer 和 Supplier。當(dāng)時只是腦子里學(xué)會了,沒有應(yīng)用到具體的項(xiàng)目中,下次再遇到的時候還是一臉懵逼,不知道大家有沒有這種感受。
所以我們需要總結(jié)經(jīng)驗(yàn)教訓(xùn),一定要將代碼的原理搞懂,然后多敲幾遍,爭取應(yīng)用到自己的項(xiàng)目中,提升自己的編碼能力。
本文轉(zhuǎn)載自微信公眾號「阿Q說代碼」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系阿Q說代碼公眾號。