如果還不懂如何使用 Consumer 接口,來公司我當面給你講!

背景

沒錯,我還在做 XXXX 項目,還在與第三方對接接口,不同的是這次是對自己業務邏輯的處理。

在開發過程中我遇到這麼一個問題:

表結構:
一張主表A ,一張關聯表B ,表 A 中存儲着表 B 記錄的狀態。

場景:
第一步創建主表數據,插入A表;第二步調用第三方接口插入B表同時更新A表的狀態。此時大家應該都會想到在進行第二步的時候需要做好數據的冪等性。這樣的話就會存在以下幾種情況:

一、B表中不存在與A表關聯的數據,此時需要調用第三方接口,插入B表同時更新A表的狀態;

二、B表中存在與A表關聯的數據;

  1. A表中的狀態爲處理中:直接返回處理中字樣;
  2. A表中的狀態爲處理成功:直接返回成功的字樣;
  3. A表中的狀態爲處理失敗:此時需要調用第三方接口,更新B表同時更新A表的狀態;

代碼實現

首先我是這樣編寫的僞代碼

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 "處理成功";
    }
    //失敗的操作
    //請求第三方接口並解析響應結果
    ......
    if (ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode())) {
        ......
        //更新B表操作
        bb.setStatus(Constants.STATUS_ING);
        mapper.updateById(bb);

        //更新A表的狀態
        a.setStatus(Constants.STATUS_ING);
        aMapper.updateById(a);
    }
    
} else {
    //請求第三方接口並解析響應結果
    ......
    if (ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode())) {
        ......
        //插入B表操作
        bb.setStatus(Constants.STATUS_ING);
        mapper.insert(bb);

        //更新A表的狀態
        a.setStatus(Constants.STATUS_ING);
        aMapper.updateById(a);
    }
}

不知道細心的小夥伴是否發現,存在B表記錄並且狀態爲“失敗”的情況和不存在B表的情況除了插入B表或者更新B表的操作之外,其餘的操作都是相同的。

如果我們想要將公共的部分抽取出來,發現都比較零散,還不如不抽取,但是不抽取代碼又存在大量重複的代碼不符合我的風格。於是我便將手伸向了 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){
    //請求第三方接口並解析響應結果
    ......
    if (ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode())) {
        ......
        bb.setStatus(Constants.STATUS_ING);
    
        consumer.accept(bb);

        //更新A表的狀態
        a.setStatus(Constants.STATUS_ING);
        aMapper.updateById(a);
    }
}

看到這,如果大家都已經看懂了,那麼恭喜你,說明你對 Consumer 的使用已經全部掌握了。如果你還存在一絲絲的疑慮,那麼就接着往下看,我們將介紹一下四種常見的函數式接口。

函數式接口

那什麼是函數式接口呢?函數式接口是隻有一個抽象方法(Object的方法除外),但是可以有多個非抽象方法的接口,它表達的是一種邏輯上的單一功能。

@FunctionalInterface

@FunctionalInterface 註解用來表示該接口是函數式接口。它有助於及早發現函數式接口中出現的或接口繼承的不適當的方法聲明。

如果接口用該註解來註釋,但實際上不是函數式接口,則會在編譯時報錯。

Consumer

我們一般稱之爲“消費者”,它表示接受單個輸入參數但不返回結果的操作。不同於其它函數式接口,Consumer 預期通過副作用進行操作。

那什麼又是副作用呢?說一下我所理解的副作用,副作用其實就是一個函數是否會修改它範圍之外的資源,如果有就叫有副作用,反之爲沒有副作用。比如修改全局變量,修改輸入參數所引用的對象等。

@FunctionalInterface
public interface Consumer<T> {

    /**
     *  對給定的參數執行此操作。
     */
    void accept(T t);

    /**
     * 
     *  返回一個組合的 Consumer ,依次執行此操作,然後執行after操作。
     *  如果執行任一操作會拋出異常,它將被轉發到組合操作的調用者。
     *  如果執行此操作會引發異常,則不會執行after操作。
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

正如我們案例中遇到的場景,我們只需要將要執行的邏輯方法當作參數傳入 getResponse() 中,然後在該方法中執行 accept() 方法進行消費即可。如果還不理解,我們可以把它轉換爲匿名內部類的調用方式。

 getResponse(dto, response, new Consumer<B>() {
    @Override
    public void accept(B bb) {
      mapper.insert(bb);
    }
});

當調用accept() 方法的時候就會去調用匿名內部類的方法了,也就是我們傳入 getResponse() 的邏輯方法。

Supplier

我們一般稱之爲“生產者”,沒有參數輸入,但是能返回結果,爲結果的提供者。

@FunctionalInterface
public interface Supplier<T> {

    /**
     *  獲取一個結果
     */
    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

我把它稱爲“轉換者”,表示接收一個參數通過處理之後返回一個結果的函數。

@FunctionalInterface
public interface Function<T, R> {

    /**
     *  將 T 類型的參數傳入,經過函數表達式的計算,返回 R 類型的結果
     */
    R apply(T t);

    /**
     * 返回一個組合函數,先將參數應用於 before 函數,然後將結果應用於當前函數,返回最終結果。
     * 如果對任一函數的求值引發異常,則會將其轉發給組合函數的調用方。
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    /**
     * 返回一個組合函數,先將參數應用與當前函數,然後將結果應用於 after 函數,返回最終的結果。
     * 如果對任一函數的求值引發異常,則會將其轉發給組合函數的調用方。 
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    /**
     *  返回始終返回其輸入參數的函數。
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

我們在 lambda 表達式中應用比較多,所以我們來簡單演示下:

@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 接收的參數就是 Function 類型, item 爲傳入參數,item.getName() 爲返回處理的結果,最後輸出結果爲

[張三, 李四, 王五]

Predicate

我們稱之爲“判斷者”,通過接收參數 T 來返回 boolean 的結果。

@FunctionalInterface
public interface Predicate<T> {

    /**
     *  接收一個參數, 判斷這個參數是否匹配某種規則, 匹配成功返回true, 匹配失敗則返回false
     */
    boolean test(T t);

    /**
     *  接收一個 Predicate 類型的參數,用當前函數和 other 函數邏輯與判斷參數 t 是否匹配規則,成功返回true,失敗返回 false 
     *  如果當前函數返回 false,則 other 函數不進行計算
     * 在評估 Predicate 期間引發的任何異常都會轉發給調用方
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    /**
     *  返回當前Predicate取反操作之後的Predicate
     */
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    /**
     *  接收一個 Predicate 類型的參數,用當前函數和 other 函數 邏輯或 判斷參數 t 是否匹配規則,成功返回true,失敗返回 false 
     *  如果當前函數返回 true,則 other 函數不進行計算
     * 在評估 Predicate 期間引發的任何異常都會轉發給調用方
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     *  靜態方法:傳入一個參數,用來生成一個 Predicate,調用test() 方法時調的 object -> targetRef.equals(object) 函數式
     *
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

相信大家在編碼過程中經常會遇到該函數式接口,我們舉個例子來說一下:

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() 的參數爲 Predicate 類型的,返回結果爲:李四

看到這兒,我們常見的四種函數式接口就已經介紹完了。說實話,函數式接口我已經看過好幾遍了,尤其是 ConsumerSupplier。當時只是腦子裏學會了,沒有應用到具體的項目中,下次再遇到的時候還是一臉懵逼,不知道大家有沒有這種感受。

所以我們需要總結經驗教訓,一定要將代碼的原理搞懂,然後多敲幾遍,爭取應用到自己的項目中,提升自己的編碼能力。

以上就是今天的全部內容了,如果你有不同的意見或者更好的idea,歡迎聯繫阿Q,添加阿Q可以加入技術交流羣參與討論呦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章