一起來學Java8(七)——Stream(中)

一起來學Java8(七)——Stream(上)中我們了解到了Stream對象的常用方法以及用法?,F(xiàn)在一起來深入了解下Stream.collect()方法的使用

collect基本用法

collect意思為收集,它是對Stream中的元素進行收集和歸納,返回一個新的集合對象。先來看一個簡單例子:

public class CollectTest {

    @Data
    @AllArgsConstructor
    static class Goods {
        private String goodsName;
        private int price;
    }
    
    public static void main(String[] args) {
        List<Goods> list = Arrays.asList(
                new Goods("iphoneX", 4000)
                , new Goods("mate30 pro", 5999)
                , new Goods("redmek20", 2999)
                );
        List<String> nameList = list.stream()
            .map(Goods::getGoodsName)
            .collect(Collectors.toList());
    }

}

在這個例子中,通過map方法返回商品名稱,然后把所有的商品名稱放到了List對象中。

查看源碼發(fā)現(xiàn),collect方法由兩個重載方法組成。

  • 方法1:
<R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);
  • 方法2:
<R, A> R collect(Collector<? super T, A, R> collector);

其中用的最多的是方法2,這個方法可以看做是方法1的快捷方式,因為Collector中同樣提供了Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner這三個參數(shù),不難猜測其底層還是要用到方法1對應(yīng)的實現(xiàn)。

我們可以先從collect(Collector<? super T, A, R> collector)開始入手,通過這個再去慢慢了解方法1的用法。

Collectors

Stream.collect(Collector<? super T, A, R> collector)方法的參數(shù)Collector對象主要由Collectors類提供。Collectors類里面包含了一系列的靜態(tài)方法,用來返回Collector對象,常用的方法如下列表所示:

方法名稱 描述
averagingXX 求平均數(shù)
counting 求集合中元素個數(shù)
groupingBy 對集合進行分組
joining 對集合元素進行拼接
mapping 可在分組的過程中再次進行值的映射
maxBy 求最大值
minBy 求最小值
partitioningBy 對元素進行分區(qū)
reducing 歸納
summarizingXX 匯總
toCollection 轉(zhuǎn)換成集合對象
toConcurrentMap 轉(zhuǎn)換成ConcurrentMap
toList 轉(zhuǎn)換成List
toMap 轉(zhuǎn)換成Map
toSet 轉(zhuǎn)換成Set

下面依次來講解下每個方法的用處。

averagingXX

averagingXX包括averagingDouble,averagingInt,averagingLong。它們表示求平均值。

double averagingInt = Stream.of(1, 2, 3)
        .collect(Collectors.averagingInt(val -> val));
System.out.println("averagingInt:" + averagingInt);

double averagingLong = Stream.of(10L, 21L, 30L)
        .collect(Collectors.averagingLong(val -> val));
System.out.println("averagingLong:" + averagingLong);

double averagingDouble = Stream.of(0.1, 0.2, 0.3)
        .collect(Collectors.averagingDouble(val -> val));
System.out.println("averagingDouble:" + averagingDouble);

它們的參數(shù)是一個函數(shù)式接口,可以使用Lambda表達式編寫,其中Lambda表達式中的參數(shù)為Stream中的元素,返回的是待求平均的數(shù)值。下面這則列子是求商品的平均值:

List<Goods> list = Arrays.asList(
                new Goods("iphoneX", 4000)
                , new Goods("mate30 pro", 5999)
                , new Goods("redmek20", 2999)
                );
        
double avgPrice = list.stream()
    .collect(Collectors.averagingInt(goods -> goods.getPrice()));
System.out.println("商品的平均價格:" + avgPrice);

summingXX

與averagingXX類似,summingXX方法用來求集合中的元素值的總和。

double summingInt = Stream.of(1, 2, 3)
        .collect(Collectors.summingInt(val -> val));
System.out.println("summingInt:" + summingInt);

double summingLong = Stream.of(10L, 21L, 30L)
        .collect(Collectors.summingLong(val -> val));
System.out.println("summingLong:" + summingLong);

double summingDouble = Stream.of(0.1, 0.2, 0.3)
        .collect(Collectors.summingDouble(val -> val));
System.out.println("summingDouble:" + summingDouble);

打印:

summingInt:6.0
summingLong:61.0
summingDouble:0.6

counting()

counting()返回集合中元素個數(shù)。

long count = Stream.of(1,2,3,4,5)
        .collect(Collectors.counting());
System.out.println("count:" + count); // 5

summarizingXX

上面講到了averagingXX(求平均)、summingXX(求和)、counting(求總數(shù)),如果我要同時獲取這三個數(shù)該怎么辦呢,可以用summarizingXX。

IntSummaryStatistics summarizingInt = Stream.of(1, 2, 3)
                .collect(Collectors.summarizingInt(val -> val));
System.out.println("平均值:" + summarizingInt.getAverage());
System.out.println("總個數(shù):" + summarizingInt.getCount());
System.out.println("總和:" + summarizingInt.getSum());
System.out.println("最大值:" + summarizingInt.getMax());
System.out.println("最小值:" + summarizingInt.getMin());

打印:

平均值:2.0
總個數(shù):3
總和:6
最大值:3
最小值:1

summarizingInt將統(tǒng)計結(jié)果放到了一個IntSummaryStatistics對象里面,在對象中可以獲取不同的統(tǒng)計信息。

groupingBy()

groupingBy()是對集合中的元素進行分組,由三個重載方法組成

  • 重載1: groupingBy(Function)
  • 重載2: groupingBy(Function, Collector)
  • 重載3: groupingBy(Function, Supplier, Collector)

其中重載1調(diào)用了重載2,重載2調(diào)用重載3,因此最終都會執(zhí)行到重載3中來。

首先看下重載1groupingBy(Function)的用法,這個方法默認分組到新的List中,下面這個例子對商品類型進行分組,同樣的類型的商品放到一個List中。

@Data
@AllArgsConstructor
static class Goods {
    private String goodsName;
    // 類型,1:手機,2:電腦
    private int type;
    @Override
    public String toString() {
        return goodsName;
    }
}

public static void main(String[] args) {
    List<Goods> list = Arrays.asList(
            new Goods("iphoneX", 1)
            , new Goods("mate30 pro", 1)
            , new Goods("thinkpad T400", 2)
            , new Goods("macbook pro", 2)
            );
    
    Map<Integer, List<Goods>> goodsListMap = list.stream()
        .collect(Collectors.groupingBy(Goods::getType));
    goodsListMap.forEach((key, value) -> {
        System.out.println("類型" + key + ":" + value);
    });
}

打印:

類型1:[iphoneX, mate30 pro]
類型2:[thinkpad T400, macbook pro]

上面說到了groupingBy(Function)實際上是調(diào)用了groupingBy(Function, Collector),其中第二個參數(shù)Collector決定了轉(zhuǎn)換到哪里,默認是toList(),參見groupingBy(Function)的源碼:

public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) {
        return groupingBy(classifier, toList());
    }

因此我們可以調(diào)用groupingBy(Function, Collector)手動指定Collector,假設(shè)我們要把轉(zhuǎn)換后的元素放到Set當中,可以這樣寫:

Map<Integer, Set<Goods>> goodsListMap = list.stream()
        .collect(Collectors.groupingBy(Goods::getType, Collectors.toSet()));

查看重載2方法源碼,發(fā)現(xiàn)其調(diào)用了重載3:

public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }

其中Goods::getType對應(yīng)classifier,Collectors.toSet()對應(yīng)downstream。中間那個參數(shù)HashMap::new意思很明顯了,即返回的Map的具體實現(xiàn)類是哪個,如果要改成LinkedHashMap,可以這樣寫:

LinkedHashMap<Integer, Set<Goods>> goodsListMap = list.stream()
        .collect(Collectors.groupingBy(Goods::getType, LinkedHashMap::new, Collectors.toSet()));
        

這正是重載3的使用方式。

Collectors中的groupingByConcurrent方法正是基于重載3而來,中間的代碼改成了ConcurrentHashMap::new而已。

public static <T, K>
    Collector<T, ?, ConcurrentMap<K, List<T>>>
    groupingByConcurrent(Function<? super T, ? extends K> classifier) {
        return groupingByConcurrent(classifier, ConcurrentHashMap::new, toList());
    }

groupingBy方法中的Collector參數(shù)不僅僅只可以toList(),toSet(),它還有更加靈活的用法,之前我們轉(zhuǎn)換的都是Map<Integer, List<Goods>>形式,value中存放的是集合對象,如果不想要那么多屬性,只想要對象里面的商品名稱,,也就是說我們想得到Map<Integer, List<String>>,其中key為商品類型,value為商品名稱集合。

這個時候Collectors.mapping()就派上用場了,我們使用groupingBy(Function, Collector)方法,第二參數(shù)傳Collectors.mapping()

Map<Integer, List<String>> goodsListMap = 
list.stream()
    .collect(
         Collectors.groupingBy(
            Goods::getType, 
            Collectors.mapping(Goods::getGoodsName, Collectors.toList())
         )
    );

mapping()方法有兩個參數(shù),第一參數(shù)指定返回的屬性,第二個參數(shù)指定返回哪種集合。

joining

joining方法可以把Stream中的元素拼接起來。

List<String> list = Arrays.asList("hello", "world");
String str = list.stream().collect(Collectors.joining());
System.out.println(str); // 打?。篽elloworld

還可以指定分隔符:

List<String> list = Arrays.asList("hello", "world");
String str = list.stream().collect(Collectors.joining(","));
System.out.println(str); // 打印:hello,world

除此之外,String類提供了一個join方法,功能是一樣的

String str2 = String.join(",", list);
System.out.println(str2);

maxBy&minBy

  • maxBy:找出Stream中最大的元素
@Data
@AllArgsConstructor
static class Goods {
    private String goodsName;
    private int price;
}

public static void main(String[] args) {
    List<Goods> list = Arrays.asList(
            new Goods("iphoneX", 4000)
            , new Goods("mate30 pro", 5999)
            , new Goods("redmek20", 2999)
            );
    
    Goods maxPriceGoods = list.stream()
        .collect(
            Collectors.maxBy(
                Comparator.comparing(Goods::getPrice)
            )
        )
        .orElse(null);
    System.out.println("最貴的商品:" + maxPriceGoods);
}

上面的例子演示了查找最貴的商品,Collectors.maxBy()方法需要傳入一個比較器,需要根據(jù)商品的價格來比較。

同理,找到最便宜的商品只需把maxBy替換成minBy即可。

partitioningBy

partitioningBy方法表示分區(qū),它將根據(jù)條件將Stream中的元素分成兩部分,并分別放入到一個Map當中,Map的key為Boolean類型,key為true部分存放滿足條件的元素,key為false存放不滿足條件的元素。

{
    true -> 符合條件的元素
    false -> 不符合條件的元素
}

partitioningBy方法由兩個重載方法組成

  • 重載1:partitioningBy(Predicate)
  • 重載2:partitioningBy(Predicate, Collector)

其中重載1會調(diào)用重載2,因此最終還是調(diào)用了重載2方法,我們先看下重載1方法。

下面這個例子根據(jù)商品類型,將商品劃分為手機類商品和非手機類商品。

@Data
@AllArgsConstructor
static class Goods {
    private String goodsName;
    // 類型,1:手機,2:電腦
    private int type;
    @Override
    public String toString() {
        return goodsName;
    }
}

public static void main(String[] args) {
    List<Goods> list = Arrays.asList(
            new Goods("iphoneX", 1)
            , new Goods("mate30 pro", 1)
            , new Goods("thinkpad T400", 2)
            , new Goods("macbook pro", 2)
            );
    
    // 手機歸為一類,非手機商品歸為一類
    // true -> 手機類商品
    // false -> 非手機類商品
    Map<Boolean, List<Goods>> goodsMap = list.stream()
        .collect(
            Collectors.partitioningBy(goods -> goods.getType() == 1)
        );
    // 獲取手機類商品
    List<Goods> mobileGoods = goodsMap.get(true);
    System.out.println(mobileGoods);
}

partitioningBy(Predicate, Collector)方法的第二個參數(shù)可以用來指定集合元素,默認使用的List存放,如果要使用Set存放,可以這樣寫:

Map<Boolean, Set<Goods>> goodsMap = list.stream()
    .collect(
        Collectors.partitioningBy(
            goods -> goods.getType() == 1
            // 指定收集類型
            , Collectors.toSet())
    );

toList & toSet & toCollection

toList和toSet可以將Stream中的元素轉(zhuǎn)換成List、Set集合,這是用的比較多的兩個方法。

Stream<Goods> stream = Stream.of(
        new Goods("iphoneX", 4000)
        , new Goods("mate30 pro", 5999)
        , new Goods("redmek20", 2999)
        );

List<Goods> list = stream.collect(Collectors.toList());
Set<Goods> set = stream.collect(Collectors.toSet());

默認情況下,toList返回的是ArrayList,toSet返回的是HashSet,如果要返回其它類型的集合比如LinkedList,可以使用toCollection,它可以讓開發(fā)者自己指定需要哪種集合。

LinkedList<Goods> linkedList = stream.collect(Collectors.toCollection(LinkedList::new));

toConcurrentMap

toConcurrentMap方法是將Stream轉(zhuǎn)換成ConcurrentMap,它由三個重載方法組成

  • 重載1:toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
  • 重載2:toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
  • 重載3:toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)

其中重載1調(diào)用重載2,重載2調(diào)用重載3,最終都會執(zhí)行到重載3方法上來。

先看重載1,提供了兩個參數(shù)

  • keyMapper:指定ConcurrentMap中的key值
  • valueMapper:指定key對應(yīng)的value

下面這個例子是將商品的名稱作為key,價格作為value

List<Goods> list = Arrays.asList(
        new Goods("iphoneX", 4000)
        , new Goods("mate30 pro", 5999)
        , new Goods("redmek20", 2999)
);
ConcurrentMap<String, Integer> goodsMap = list.stream()
        .collect(
                Collectors.toConcurrentMap(Goods::getGoodsName, Goods::getPrice)
        );
System.out.println(goodsMap);

打?。?/p>

{mate30 pro=5999, iphoneX=4000, redmek20=2999}

注意:這個方法要求key不能重復(fù),如果有重復(fù)的key,會拋IllegalStateException異常,如果有key重復(fù),需要使用toConcurrentMap(Function, Function, BinaryOperator),即重載2

再來看下重載2:toConcurrentMap(Function, Function, BinaryOperator),這個方法前兩個參數(shù)跟重載1一樣,第三個參數(shù)用來處理key沖突的情況,讓開發(fā)者選擇一個value值返回。

List<Goods> list = Arrays.asList(
        new Goods("iphoneX", 4000)
        , new Goods("mate30 pro", 5999)
        , new Goods("mate30 pro", 6000) // 這里有兩個沖突了
        , new Goods("redmek20", 2999)
);
ConcurrentMap<String, Integer> goodsMap = list.stream()
        .collect(
                Collectors.toConcurrentMap(Goods::getGoodsName, Goods::getPrice, new BinaryOperator<Integer>() {
                    @Override
                    public Integer apply(Integer price1, Integer price2) {
                        // 選擇價格貴的返回
                        return Math.max(price1, price2);
                    }
                })
        );
System.out.println(goodsMap);

打印:{mate30 pro=6000, iphoneX=4000, redmek20=2999}

這個例子中mate30 pro作為key重復(fù)了,在BinaryOperator中,我們選擇價格高的那一條數(shù)據(jù)返回。

最后看下重載3,相比于重載2,又多了一個參數(shù)Supplier,它可以讓開發(fā)者指定返回一種ConcurrentMap

重載2調(diào)用重載3,默認使用的是ConcurrentMap::new。

注意:第四個參數(shù)必須是ConcurrentMap或ConcurrentMap的子類

小節(jié)

本篇主要講解了Stream.collect的用法,以及Collectors類中靜態(tài)方法的使用,在下一篇文章中,我們將詳細講解關(guān)于reduce的相關(guān)用法。

定期分享技術(shù)干貨,一起學習,一起進步!微信公眾號:猿敲月下碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容