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

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

collect基本用法

collect意思為收集食茎,它是對Stream中的元素進(jìn)行收集和歸納损俭,返回一個新的集合對象瑞侮。先來看一個簡單例子:

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 對集合進(jìn)行分組
joining 對集合元素進(jìn)行拼接
mapping 可在分組的過程中再次進(jìn)行值的映射
maxBy 求最大值
minBy 求最小值
partitioningBy 對元素進(jìn)行分區(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表達(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("商品的平均價格:" + 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()是對集合中的元素進(jìn)行分組嗽测,由三個重載方法組成

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

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

首先看下重載1groupingBy(Function)的用法疏魏,這個方法默認(rèn)分組到新的List中,下面這個例子對商品類型進(jìn)行分組晤愧,同樣的類型的商品放到一個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);
    });
}

打又焕濉:

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

上面說到了groupingBy(Function)實際上是調(diào)用了groupingBy(Function, Collector),其中第二個參數(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)手動指定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對應(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); // 打臃磁ā:helloworld

還可以指定分隔符:

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當(dāng)中,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ù)商品類型,將商品劃分為手機(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)方法的第二個參數(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集合,這是用的比較多的兩個方法挖炬。

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钓猬,它由三個重載方法組成

  • 重載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);

打右煅恪:

{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,默認(rèn)使用的是ConcurrentMap::new役衡。

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

小節(jié)

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

定期分享技術(shù)干貨,一起學(xué)習(xí)棵介,一起進(jìn)步帽撑!微信公眾號:猿敲月下碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鞍时,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扣蜻,老刑警劉巖逆巍,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異莽使,居然都是意外死亡锐极,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門芳肌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灵再,“玉大人,你說我怎么就攤上這事亿笤◆崆ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵净薛,是天一觀的道長汪榔。 經(jīng)常有香客問我,道長肃拜,這世上最難降的妖魔是什么痴腌? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮燃领,結(jié)果婚禮上士聪,老公的妹妹穿的比我還像新娘。我一直安慰自己猛蔽,他們只是感情好剥悟,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般懦胞。 火紅的嫁衣襯著肌膚如雪替久。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天躏尉,我揣著相機(jī)與錄音蚯根,去河邊找鬼。 笑死胀糜,一個胖子當(dāng)著我的面吹牛颅拦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播教藻,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼距帅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了括堤?” 一聲冷哼從身側(cè)響起碌秸,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悄窃,沒想到半個月后讥电,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡轧抗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年恩敌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片横媚。...
    茶點(diǎn)故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡纠炮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灯蝴,到底是詐尸還是另有隱情恢口,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布穷躁,位于F島的核電站弧蝇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏折砸。R本人自食惡果不足惜看疗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望睦授。 院中可真熱鬧两芳,春花似錦、人聲如沸去枷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至竖螃,卻和暖如春淑廊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背特咆。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工季惩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腻格。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓画拾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親菜职。 傳聞我的和親對象是個殘疾皇子青抛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容