網頁

2019/5/15

Java 8 Stream <R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner) 簡單範例

下面的簡單範例說明如何使用很難用的
Stream.collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)

你有一個裝載多個整數的List來源,然後要從中取出符合條件的元素放在另一個List中。

例如下面的List<Integer>裝有1-6的整數,我想要取出大於3的整數並放到另一個List<Integer>

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);

List<Integer> result = list.stream()
        .filter(e -> e > 3)
        .collect(() -> new ArrayList<>(), // supplier
                    (c, e) -> c.add(e),         // accumulator
                    (c1, c2) -> c1.addAll(c2)); // combiner


System.out.println(result); // [4, 5, 6]

注意在Eclipse中你可能會發現上面的c.add(e)會報錯,請忽略錯誤直接將程式碼寫完,因為目前Eclipse對lambda語法的支援如自動完成仍有些bug。


<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)接收三個參數如下


supplier是一個工廠方法,為無輸入參數但有回傳值的函式,你可以把supplier稱做容器供應商,負責提供裝載結果元素的新容器,supplier函式的回傳值即為新容器,也就是上面的new ArrayList<>()。supplier的原本樣貌如下:

Supplier<ArrayList<Integer>> supplier = new Supplier<ArrayList<Integer>>() {

    @Override
    public ArrayList<Integer> get() {
        return new ArrayList<>();
    }

};

accumulator為接收兩個參數但無回傳值的函式,
第一個參數c (container)為裝載元素的容器,也就是supplier提供的新容器ArrayList<Integer>。此參數的型態必須與supplier的回傳值相同。
第二個參數e (element)為要被裝載的元素,也就是被filter()條件所過濾後的元素,也就是filter()過濾後的的整數。

因為collect(...)通常是要將修改後的元素放在新容器中,所以像是List等集合通常都是用add()方法將元素加入。accumulator的原本樣貌如下:

BiConsumer<ArrayList<Integer>, Integer> accumulator = new BiConsumer<ArrayList<Integer>, Integer>() {
   
    @Override
    public void accept(ArrayList<Integer> c, Integer e) {
        c.add(e);
    }
    
};

combiner為接收兩個參數但無回傳值的函式,兩個參數都是supplier提供的新容器ArrayList<Integer>。負責把所有的新容器中的元素合併。注意combiner只有在平行串流(parallel stream)才派得上用場,在平行處理時,每個元素會在accumulator函式中被放在不同的新容器中,最後透過combiner合併,因此combiner函式必須滿足結合性(Associativity)。combiner的原本樣貌如下:

BiConsumer<ArrayList<Integer>, ArrayList<Integer>> combiner = new BiConsumer<ArrayList<Integer>, ArrayList<Integer>>() {

    @Override
    public void accept(ArrayList<Integer> c1, ArrayList<Integer> c2) {
        c1.addAll(c2);
    }

};

所以lambda原本實際上長這樣:

BiConsumer<ArrayList<Integer>, ArrayList<Integer>> combiner = new BiConsumer<ArrayList<Integer>, ArrayList<Integer>>() {

    @Override
    public void accept(ArrayList<Integer> c1, ArrayList<Integer> c2) {
        c1.addAll(c2);
    }

};

Supplier<ArrayList<Integer>> supplier = new Supplier<ArrayList<Integer>>() {
    @Override
    public ArrayList<Integer> get() {
        return new ArrayList<>();
    }
};

BiConsumer<ArrayList<Integer>, Integer> accumulator = new BiConsumer<ArrayList<Integer>, Integer>() {
    @Override
    public void accept(ArrayList<Integer> c, Integer e) {
        c.add(e);
    }
};

BiConsumer<ArrayList<Integer>, ArrayList<Integer>> combiner = new BiConsumer<ArrayList<Integer>, ArrayList<Integer>>() {
    @Override
    public void accept(ArrayList<Integer> c1, ArrayList<Integer> c2) {
        c1.addAll(c2);
    }
};

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);

List<Integer> result = list.stream()
        .filter(e -> e > 3)
        .collect(supplier, accumulator, combiner);


System.out.println(result); // [4, 5, 6]

平行處理

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);

List<Integer> result = list.stream().parallel() 
        .filter(e -> e > 3)
        .collect(() -> new ArrayList<>(), // supplier
                    (c, e) -> c.add(e),         // accumulator
                    (c1, c2) -> c1.addAll(c2)); // combiner

System.out.println(result); // [4, 5, 6]

方法參考寫法。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);

List<Integer> result = list.stream().parallel()
        .filter(e -> e > 3)
        .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

System.out.println(result); // [4, 5, 6]

參考:

沒有留言:

張貼留言