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

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

collect基本用法

collect意思為收集,它是對(duì)Stream中的元素進(jìn)行收集和歸納,返回一個(gè)新的集合對(duì)象。先來看一個(gè)簡(jiǎn)單例子:

public class CollectTest {

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());
    }

}

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

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

  • 方法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,這個(gè)方法可以看做是方法1的快捷方式,因?yàn)镃ollector中同樣提供了Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner這三個(gè)參數(shù),不難猜測(cè)其底層還是要用到方法1對(duì)應(yīng)的實(shí)現(xiàn)。

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

Collectors

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

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

下面依次來講解下每個(gè)方法的用處。

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ù)是一個(gè)函數(shù)式接口,可以使用Lambda表達(dá)式編寫,其中Lambda表達(dá)式中的參數(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("商品的平均價(jià)格:" + 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);

打?。?/p>

summingInt:6.0
summingLong:61.0
summingDouble:0.6

counting()
counting()返回集合中元素個(gè)數(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í)獲取這三個(gè)數(shù)該怎么辦呢,可以用summarizingXX。

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

打?。?/p>

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

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

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

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

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

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

@Data
@AllArgsConstructor
static class Goods {
    private String goodsName;
    // 類型,1:手機(jī),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);
    });
}

打?。?/p>

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

上面說到了groupingBy(Function)實(shí)際上是調(diào)用了groupingBy(Function, Collector),其中第二個(gè)參數(shù)Collector決定了轉(zhuǎn)換到哪里,默認(rè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)手動(dòng)指定Collector,假設(shè)我們要把轉(zhuǎn)換后的元素放到Set當(dāng)中,可以這樣寫:

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對(duì)應(yīng)classifier,Collectors.toSet()對(duì)應(yīng)downstream。中間那個(gè)參數(shù)HashMap::new意思很明顯了,即返回的Map的具體實(shí)現(xiàn)類是哪個(gè),如果要改成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中存放的是集合對(duì)象,如果不想要那么多屬性,只想要對(duì)象里面的商品名稱,,也就是說我們想得到Map<Integer, List<String>>,其中key為商品類型,value為商品名稱集合。

這個(gè)時(shí)候Collectors.mapping()就派上用場(chǎng)了,我們使用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()方法有兩個(gè)參數(shù),第一參數(shù)指定返回的屬性,第二個(gè)參數(shù)指定返回哪種集合。

joining

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

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

還可以指定分隔符:

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

除此之外,String類提供了一個(gè)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()方法需要傳入一個(gè)比較器,需要根據(jù)商品的價(jià)格來比較。

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

partitioningBy

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

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

partitioningBy方法由兩個(gè)重載方法組成

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

其中重載1會(huì)調(diào)用重載2,因此最終還是調(diào)用了重載2方法,我們先看下重載1方法。
下面這個(gè)例子根據(jù)商品類型,將商品劃分為手機(jī)類商品和非手機(jī)類商品。

@Data
@AllArgsConstructor
static class Goods {
    private String goodsName;
    // 類型,1:手機(jī),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)
            );
    
    // 手機(jī)歸為一類,非手機(jī)商品歸為一類
    // true -> 手機(jī)類商品
    // false -> 非手機(jī)類商品
    Map<Boolean, List<Goods>> goodsMap = list.stream()
        .collect(
            Collectors.partitioningBy(goods -> goods.getType() == 1)
        );
    // 獲取手機(jī)類商品
    List<Goods> mobileGoods = goodsMap.get(true);
    System.out.println(mobileGoods);
}

partitioningBy(Predicate, Collector)方法的第二個(gè)參數(shù)可以用來指定集合元素,默認(rèn)使用的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集合,這是用的比較多的兩個(gè)方法。

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());

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

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

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

  • 重載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,最終都會(huì)執(zhí)行到重載3方法上來。

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

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

下面這個(gè)例子是將商品的名稱作為key,價(jià)格作為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}

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

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

List<Goods> list = Arrays.asList(
        new Goods("iphoneX", 4000)
        , new Goods("mate30 pro", 5999)
        , new Goods("mate30 pro", 6000) // 這里有兩個(gè)沖突了
        , 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) {
                        // 選擇價(jià)格貴的返回
                        return Math.max(price1, price2);
                    }
                })
        );
System.out.println(goodsMap);

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

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

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

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

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

小節(jié)

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

https://shimo.im/docs/gGQHg8xPC3X8cPHV/ 《2020年最新Java架構(gòu)師系統(tǒng)進(jìn)階資料免費(fèi)領(lǐng)取》,可復(fù)制鏈接后用石墨文檔 App 或小程序打開

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

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

  • 在一起來學(xué)Java8(七)——Stream(上)中我們了解到了Stream對(duì)象的常用方法以及用法?,F(xiàn)在一起來深入了...
    猿敲月下碼閱讀 857評(píng)論 0 51
  • Int Double Long 設(shè)置特定的stream類型, 提高性能,增加特定的函數(shù) 無存儲(chǔ)。stream不是一...
    patrick002閱讀 1,327評(píng)論 0 0
  • Java8 in action 沒有共享的可變數(shù)據(jù),將方法和函數(shù)即代碼傳遞給其他方法的能力就是我們平常所說的函數(shù)式...
    鐵牛很鐵閱讀 1,357評(píng)論 1 2
  • Stream API是Java8中處理集合的關(guān)鍵組件,提供了各種豐富的函數(shù)式操作。 Stream的創(chuàng)建 任何集合都...
    fengshunli閱讀 611評(píng)論 0 2
  • 原文地址: 深藍(lán)至尊 一. 流式處理簡(jiǎn)介 在我接觸到j(luò)ava8流式處理的時(shí)候,我的第一感覺是流式處理讓集合操作變得...
    咻咻咻i閱讀 1,265評(píng)論 0 0

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