Java基礎系列-Collector和Collectors


原創作品,可以轉載,但是請標注出處地址:https://www.cnblogs.com/V1haoge/p/10748925.html

一、概述

Collector是專門用來作為Stream的collect方法的參數的。

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    <R, A> R collect(Collector<? super T, A, R> collector);
}

而Collectors是作為生產具體Collector的工具類。

二、Collector

Collector主要包含五個參數,它的行為也是由這五個參數來定義的,如下所示:

public interface Collector<T, A, R> {
    // supplier參數用於生成結果容器,容器類型為A
    Supplier<A> supplier();
    // accumulator用於消費元素,也就是歸納元素,這里的T就是元素,它會將流中的元素一個一個與結果容器A發生操作
    BiConsumer<A, T> accumulator();
    // combiner用於兩個兩個合並並行執行的線程的執行結果,將其合並為一個最終結果A
    BinaryOperator<A> combiner();
    // finisher用於將之前整合完的結果R轉換成為A
    Function<A, R> finisher();
    // characteristics表示當前Collector的特征值,這是個不可變Set
    Set<Characteristics> characteristics();
}

Collector擁有兩個of方法用於生成Collector實例,其中一個擁有上面所有五個參數,另一個四個參數,不包括finisher。

public interface Collector<T, A, R> {
    // 四參方法,用於生成一個Collector,T代表流中的一個一個元素,R代表最終的結果
    public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
                                              BiConsumer<R, T> accumulator,
                                              BinaryOperator<R> combiner,
                                              Characteristics... characteristics) {/*...*/}
    // 五參方法,用於生成一個Collector,T代表流中的一個一個元素,A代表中間結果,R代表最終結果,finisher用於將A轉換為R                                          
    public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
                                                 BiConsumer<A, T> accumulator,
                                                 BinaryOperator<A> combiner,
                                                 Function<A, R> finisher,
                                                 Characteristics... characteristics) {/*...*/}                                              
}

Characteristics:這個特征值是一個枚舉,擁有三個值:CONCURRENT(多線程並行),UNORDERED(無序),IDENTITY_FINISH(無需轉換結果)。其中四參of方法中沒有finisher參數,所有必有IDENTITY_FINISH特征值。

三、Collectors

Collectors是一個工具類,是JDK預實現Collector的工具類,它內部提供了多種Collector,我們可以直接拿來使用,非常方便。

5.6.1 toCollection

將流中的元素全部放置到一個集合中返回,這里使用Collection,泛指多種集合。

public class CollectorsTest {
    public static void toCollectionTest(List<String> list) {
        List<String> ll = list.stream().collect(Collectors.toCollection(LinkedList::new));
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        toCollectionTest(list);
    }
}

5.6.2 toList

將流中的元素放置到一個列表集合中去。這個列表默認為ArrayList。

public class CollectorsTest {
    public static void toListTest(List<String> list) {
        List<String> ll = list.stream().collect(Collectors.toList());
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        toListTest(list);
    }
}

5.6.3 toSet

將流中的元素放置到一個無序集set中去。默認為HashSet。

public class CollectorsTest {
    public static void toSetTest(List<String> list) {
        Set<String> ss = list.stream().collect(Collectors.toSet());
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        toSetTest(list);
    }
}

5.6.4 joining

joining的目的是將流中的元素全部以字符序列的方式連接到一起,可以指定連接符,甚至是結果的前后綴。

public class CollectorsTest {
    public static void joiningTest(List<String> list){
        // 無參方法
        String s = list.stream().collect(Collectors.joining());
        System.out.println(s);
        // 指定連接符
        String ss = list.stream().collect(Collectors.joining("-"));
        System.out.println(ss);
        // 指定連接符和前后綴
        String sss = list.stream().collect(Collectors.joining("-","S","E"));
        System.out.println(sss);
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        joiningTest(list);
    }
}

執行結果:

1234567891101212121121asdaa3e3e3e2321eew
123-456-789-1101-212121121-asdaa-3e3e3e-2321eew
S123-456-789-1101-212121121-asdaa-3e3e3e-2321eewE

StringJoiner:這是一個字符串連接器,可以定義連接符和前后綴,正好適用於實現第三種joining方法。

5.6.5 mapping

這個映射是首先對流中的每個元素進行映射,即類型轉換,然后再將新元素以給定的Collector進行歸納。

public class CollectorsTest {
    public static void mapingTest(List<String> list){
        List<Integer> ll = list.stream().limit(5).collect(Collectors.mapping(Integer::valueOf,Collectors.toList()));
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        mapingTest(list);
    }
}

實例中截取字符串列表的前5個元素,將其分別轉換為Integer類型,然后放到一個List中返回。

5.6.6 collectingAndThen

該方法是在歸納動作結束之后,對歸納的結果進行再處理。

public class CollectorsTest {
    public static void collectingAndThenTest(List<String> list){
        int length = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(),e -> e.size()));
        System.out.println(length);
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        collectingAndThenTest(list);
    }
}

執行結果為:

8

5.6.7 counting

該方法用於計數。

public class CollectorsTest {
    public static void countingTest(List<String> list){
        long size = list.stream().collect(Collectors.counting());
        System.out.println(size);
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        countingTest(list);
    }
}

結果:

8

5.6.8 minBy/maxBy

生成一個用於獲取最小/最大值的Optional結果的Collector。

public class CollectorsTest {
    public static void maxByAndMinByTest(List<String> list){
        System.out.println(list.stream().collect(Collectors.maxBy((a,b) -> a.length()-b.length())));
        System.out.println(list.stream().collect(Collectors.minBy((a,b) -> a.length()-b.length())));
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        maxByAndMinByTest(list);
    }
}

執行結果為:

Optional[212121121]
Optional[123]

5.6.9 summingInt/summingLong/summingDouble

生成一個用於求元素和的Collector,首先通過給定的mapper將元素轉換類型,然后再求和。

參數的作用就是將元素轉換為指定的類型,最后結果與轉換后類型一致。

public class CollectorsTest {
    public static void summingTest(List<String> list){
        int i = list.stream().limit(3).collect(Collectors.summingInt(Integer::valueOf));
        long l = list.stream().limit(3).collect(Collectors.summingLong(Long::valueOf));
        double d = list.stream().limit(3).collect(Collectors.summingDouble(Double::valueOf));
        System.out.println(i +"\n" +l + "\n" + d);
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        summingTest(list);
    }
}

執行結果為:

1368
1368
1368.0

5.6.10 averagingInt/averagingLong/averagingDouble

生成一個用於求元素平均值的Collector,首選通過參數將元素轉換為指定的類型。

參數的作用就是將元素轉換為指定的類型,求平均值涉及到除法操作,結果一律為Double類型。

public class CollectorsTest {
    public static void averagingTest(List<String> list){
        double i = list.stream().limit(3).collect(Collectors.averagingInt(Integer::valueOf));
        double l = list.stream().limit(3).collect(Collectors.averagingLong(Long::valueOf));
        double d = list.stream().limit(3).collect(Collectors.averagingDouble(Double::valueOf));
        System.out.println(i +"\n" +l + "\n" + d);
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        averagingTest(list);
    }
}

執行結果為:

456.0
456.0
456.0

5.6.11 reducing

reducing方法有三個重載方法,其實是和Stream里的三個reduce方法對應的,二者是可以替換使用的,作用完全一致,也是對流中的元素做統計歸納作用。

public final class Collectors {
    // 無初始值的情況,返回一個可以生成Optional結果的Collector
    public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) {/*...*/}
    // 有初始值的情況,返回一個可以直接產生結果的Collector
    public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op) {/*...*/}
    // 有初始值,還有針對元素的處理方案mapper,生成一個可以直接產生結果的Collector,元素在執行結果操作op之前需要先執行mapper進行元素轉換操作
    public static <T, U> Collector<T, ?, U> reducing(U identity,
                                    Function<? super T, ? extends U> mapper,
                                    BinaryOperator<U> op) {/*...*/}
}

實例:

public class CollectorsTest {
    public static void reducingTest(List<String> list){
        System.out.println(list.stream().limit(4).map(String::length).collect(Collectors.reducing(Integer::sum)));
        System.out.println(list.stream().limit(3).map(String::length).collect(Collectors.reducing(0, Integer::sum)));
        System.out.println(list.stream().limit(4).collect(Collectors.reducing(0,String::length,Integer::sum)));
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        reducingTest(list);
    }
}
Optional[13]
9
13

效果可參見Java基礎系列-Stream

5.6.12 groupingBy

這個方法是用於生成一個擁有分組功能的Collector,它也有三個重載方法:

public final class Collectors {
    // 只需一個分組參數classifier,內部自動將結果保存到一個map中,每個map的鍵為?類型(即classifier的結果類型),值為一個list,這個list中保存在屬於這個組的元素。
    public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(
            Function<? super T, ? extends K> classifier) {/*...*/}
    // 在上面方法的基礎上增加了對流中元素的處理方式的Collector,比如上面的默認的處理方法就是Collectors.toList()
    public static <T, K, A, D>Collector<T, ?, Map<K, D>> groupingBy(
            Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream) {/*...*/}
    // 在第二個方法的基礎上再添加了結果Map的生成方法。
    public static <T, K, D, A, M extends Map<K, D>>
        Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                      Supplier<M> mapFactory,
                                      Collector<? super T, A, D> downstream) {/*...*/}
}

實例:

public class CollectorsTest {
    public static void groupingByTest(List<String> list){
        Map<Integer,List<String>> s = list.stream().collect(Collectors.groupingBy(String::length));
        Map<Integer,List<String>> ss = list.stream().collect(Collectors.groupingBy(String::length, Collectors.toList()));
        Map<Integer,Set<String>> sss = list.stream().collect(Collectors.groupingBy(String::length,HashMap::new,Collectors.toSet()));
        System.out.println(s.toString() + "\n" + ss.toString() + "\n" + sss.toString());
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        groupingByTest(list);
    }
}

執行結果為:

{3=[123, 456, 789], 4=[1101], 5=[asdaa], 6=[3e3e3e], 7=[2321eew], 9=[212121121]}
{3=[123, 456, 789], 4=[1101], 5=[asdaa], 6=[3e3e3e], 7=[2321eew], 9=[212121121]}
{3=[123, 456, 789], 4=[1101], 5=[asdaa], 6=[3e3e3e], 7=[2321eew], 9=[212121121]}

groupingBy方法還有並發版的groupingByConcurrent,功能基本一致,只是返回的Collector是並行的。

5.6.13 partitioningBy

該方法將流中的元素按照給定的校驗規則的結果分為兩個部分,放到一個map中返回,map的鍵是Boolean類型,值為元素的列表List。

該方法有兩個重載方法:

public final class Collectors {
    // 只需一個校驗參數predicate
    public static <T>
        Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {/*...*/}
    // 在上面方法的基礎上增加了對流中元素的處理方式的Collector,比如上面的默認的處理方法就是Collectors.toList()
    public static <T, D, A>
        Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
                                                        Collector<? super T, A, D> downstream) {/*...*/}
}

實例:

public class CollectorsTest {
    public static void partitioningByTest(List<String> list){
        Map<Boolean,List<String>> map = list.stream().collect(Collectors.partitioningBy(e -> e.length()>5));
        Map<Boolean,Set<String>> map2 = list.stream().collect(Collectors.partitioningBy(e -> e.length()>6,Collectors.toSet()));
        System.out.println(map.toString() + "\n" + map2.toString());
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        partitioningByTest(list);
    }
}

執行結果:

{false=[123, 456, 789, 1101, asdaa], true=[212121121, 3e3e3e, 2321eew]}
{false=[123, 456, 1101, 789, 3e3e3e, asdaa], true=[212121121, 2321eew]}

5.6.14 toMap

toMap方法是根據給定的鍵生成器和值生成器生成的鍵和值保存到一個map中返回,鍵和值的生成都依賴於元素,可以指定出現重復鍵時的處理方案和保存結果的map。

public final class Collectors {
    // 指定鍵和值的生成方式keyMapper和valueMapper
    public static <T, K, U>
        Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                        Function<? super T, ? extends U> valueMapper) {/*...*/}
    // 在上面方法的基礎上增加了對鍵發生重復時處理方式的mergeFunction,比如上面的默認的處理方法就是拋出異常
    public static <T, K, U>
        Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                        Function<? super T, ? extends U> valueMapper,
                                        BinaryOperator<U> mergeFunction) {/*...*/}
    // 在第二個方法的基礎上再添加了結果Map的生成方法。
    public static <T, K, U, M extends Map<K, U>>
        Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction,
                                    Supplier<M> mapSupplier) {/*...*/}
}

實例:

public class CollectorsTest {
    public static void toMapTest(List<String> list){
        Map<String,String> map = list.stream().limit(3).collect(Collectors.toMap(e -> e.substring(0,1),e -> e));
        Map<String,String> map1 = list.stream().collect(Collectors.toMap(e -> e.substring(0,1),e->e,(a,b)-> b));
        Map<String,String> map2 = list.stream().collect(Collectors.toMap(e -> e.substring(0,1),e->e,(a,b)-> b,HashMap::new));
        System.out.println(map.toString() + "\n" + map1.toString() + "\n" + map2.toString());
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        toMapTest(list);
    }
}

執行結果:

{1=123, 4=456, 7=789}
{a=asdaa, 1=1101, 2=2321eew, 3=3e3e3e, 4=456, 7=789}
{a=asdaa, 1=1101, 2=2321eew, 3=3e3e3e, 4=456, 7=789}

第一種方式中,如果不添加limit限制,就會拋出異常。

還有並發的版本:toConcurrentMap,同樣三種重載方法,與toMap基本一致,只是它最后使用的map是並發Map:ConcurrentHashMap。

5.6.15 summarizingInt/summarizingLong/summarizingDouble

這三個方法適用於匯總的,返回值分別是IntSummaryStatistics,LongSummaryStatistics,DoubleSummaryStatistics。

在這些返回值中包含有流中元素的指定結果的數量、和、最大值、最小值、平均值。所有僅僅針對數值結果。

public class CollectorsTest {
    public static void summarizingTest(List<String> list){
        IntSummaryStatistics intSummary = list.stream().collect(Collectors.summarizingInt(String::length));
        LongSummaryStatistics longSummary = list.stream().limit(4).collect(Collectors.summarizingLong(Long::valueOf));
        DoubleSummaryStatistics doubleSummary = list.stream().limit(3).collect(Collectors.summarizingDouble(Double::valueOf));
        System.out.println(intSummary.toString() + "\n" + longSummary.toString() + "\n" + doubleSummary.toString());
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123","456","789","1101","212121121","asdaa","3e3e3e","2321eew");
        summarizingTest(list);
    }
}

執行結果:

IntSummaryStatistics{count=8, sum=40, min=3, average=5.000000, max=9}
LongSummaryStatistics{count=4, sum=2469, min=123, average=617.250000, max=1101}
DoubleSummaryStatistics{count=3, sum=1368.000000, min=123.000000, average=456.000000, max=789.000000}

最后我們可以從返回的匯總實例中獲取到想要的匯總結果。

四、總結

整個Collectors工具類就是在為Collector服務,用於創建各種不同的Collector。部分功能與Stream中的方法重合了,為了簡化代碼,完全不必采用Collectors實現,優先Stream方法。

參考:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM