博客 / 詳情

返回

Java 常用函數式接口 —— Consumer接口

JDK提供了大量的函數式接口,方便我們開發的時候無需自己編寫接口,這些接口都比較通用,學會他們並且在工作中使用,不僅方便的解決問題,而且十分優雅。

1、接口概述

Consumer 接口也比較簡單,只有兩個方法,一個是抽象方法,一個是默認方法:

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after){
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

2、accept 方法

該方法接收一個接口泛型類型的對象,沒有返回值。

看看集合的 forEach 方法的使用:

public class ConsumerTest {
    public static void main(String[] args) {
        List<String> names = new ArrayList<String>() {
            {
                add("Tom");
                add("Jerry");
            }
        };
        names.forEach(e -> System.out.println(e));
        names.forEach(System.out::println);
    }
}

我們使用 forEach 方法的時候,該方法就是使用了 Consumer 接口作為參數。這是我們最常見的使用 Consumer 的方式。

除了打印信息,一般我們對於集合中的對象的某些數據需要更改,也經常使用forEach 遍歷,然後對於每個對象做一些業務操作。

雖然 forEach 常用,但是一般我們都很少關注 forEach 的實現,也很少自己寫個方法用到 Consumer 接口:

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

3、Consumer接口使用場景-回調

public class ConsumerTest {

    private static final Map<Integer, String> map = new HashMap<Integer, String>() {
        {
            put(10, "Tom");
            put(3, "Jerry");
        }
    };

    public static void main(String[] args) {
        //調用方法,同時編寫對結果的回調:此處僅僅打印而已
        test(3, System.out::println);
    }

    public static void test(Integer age, Consumer<String> consumer) {
        //業務處理
        System.out.println(age);

        //對處理結果的回調:下面的ifPresent參數也是Consumer接口,所有下面三種寫法都可以
        //Optional.ofNullable(map.get(age)).ifPresent(e -> consumer.accept(e));
        //Optional.ofNullable(map.get(age)).ifPresent(consumer::accept);
        Optional.ofNullable(map.get(age)).ifPresent(consumer);
    }
}

Consumer 接口一般作為方法參數來實現回調的功能,例如上面的例子,test 函數傳遞待處理的對象 age ,經過業務處理得到其它結果對象,之後調用 accept 對結果對象進行處理。

實際中回調處理的對象是根據入參得到其它結果。比如傳入姓名從數據庫查詢數據,回調函數將數據保存在其它地方,那麼這個其它地方就需要調用者自己處理,比如存文件。

4、默認方法andThen

方法源碼:

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }

使用場景:

    public static void main(String[] args) {
        Consumer<String> first = (x) -> System.out.println(x.toLowerCase());
        Consumer<String> next = (x) -> System.out.println(x.toUpperCase());
        first.andThen(next).accept("Hello World");
    }

andThen 接收一個 Consumer 接口,返回的也是一個 Consumer 接口,同樣的,調用該方法的也是 Consumer 接口。這個地方比較繞,需要對照上面兩處代碼仔細分析。

first 是一個 Consumer 接口,當調用 andThen 方法的時候,並不是執行 (T t) -> { accept(t); after.accept(t); } 這段代碼,而是返回了一個 Consumer 接口,注意它的結構是 給一個對象 t,然後大括號中消費 t 。 處理邏輯是當前 Consumer 執行 accept 方法,然後再讓 after 這個 Consumer 執行 accept 方法。理解 andThen 方法返回的結構特別重要。

first.andThen(next)執行完成,得到一個 Consumer 接口接口後,再次調用 accept("Hello World") 時,實際上對 Hello World 字符串執行的內容就是大括號裏面的內容了:accept(t); after.accept(t); 也就是上面的先輸出小寫字符串、再輸出大寫的字符串了。

5、andThen 方法使用場景

public class ConsumerTest {

    private static final Map<Integer, Consumer<String>> QUEUE = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        resolve(1, s -> System.out.println(s.toUpperCase()));
        resolve(1, s -> System.out.println(s.toLowerCase()));
        resolve(1, s -> System.out.println(s.substring(0, 2)));
        QUEUE.get(1).accept("Hello World");
    }

    public static void resolve(Integer id, Consumer<String> callback) {
        final Consumer<String> existing = QUEUE.get(id);
        if (callback == null) callback = i -> {};
        if (existing != null && callback != existing) {
            callback = existing.andThen(callback);
        }
        QUEUE.put(id, callback);
    }
}

結果如下:

HELLO WORLD
hello world
He

上面代碼中的 resolve 方法根據 id , 向 map 類型的 QUEUE 中增加多個回調函數,最後執行的時候,多個回調函數都被執行,很類似責任鏈的設計模式。可以結合上面的講解仔細理解

callback = existing.andThen(callback);

以及在執行的地方:

QUEUE.get(1).accept("Hello World");
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.