java8之用流收集數(shù)據(jù)

java8之用流收集數(shù)據(jù)

盡可能為手頭的問題探索不同的解決方案牙勘,但在通用的方案里面隅津,始終選擇最專門化的一個散劫。無論從可讀性還是性能上看韧涨,這一般都是最好的決定牍戚。

1. 規(guī)約和匯總

準備好美味的菜肴侮繁,好戲上演了!

package com.example.chapter5;

public class Dish {
    private String name;
    private boolean vegetarian;
    private int calories;
    private Type type;

    public Dish(String name, boolean vegetarian, int calories, Type type) {
        super();
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    public void setVegetarian(boolean vegetarian) {
        this.vegetarian = vegetarian;
    }

    public int getCalories() {
        return calories;
    }

    public void setCalories(int calories) {
        this.calories = calories;
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public enum Type {MEAT, FISH, OTHER}

    @Override
    public String toString() {
        return "Dish [name=" + name + ", vegetarian=" + vegetarian + ", calories=" + calories + ", type=" + type + "]";
    }
}

List<Dish> menu = Arrays.asList(new Dish("pork", false, 800, Dish.Type.MEAT),
                new Dish("beef", false, 700, Dish.Type.MEAT), new Dish("chicken", false, 400, Dish.Type.MEAT),
                new Dish("french fries", true, 800, Dish.Type.OTHER), new Dish("rice", true, 350, Dish.Type.OTHER),
                new Dish("season fruit", true, 120, Dish.Type.OTHER), new Dish("pizza", true, 550, Dish.Type.OTHER),
                new Dish("prawns", false, 300, Dish.Type.FISH), new Dish("salmon", false, 450, Dish.Type.FISH));

1.1 查找流中的最大值和最小值

        Optional<Dish> max = menu.stream().max(Comparator.comparingInt(Dish::getCalories));
        System.out.println(max.get());

        max = menu.stream().collect(maxBy(Comparator.comparingInt(Dish::getCalories)));
        System.out.println(max.get());
        
        Optional<Dish> min = menu.stream().collect(minBy(Comparator.comparingInt(Dish::getCalories)));
        System.out.println(min.get());

1.2 匯總

        menu.stream().collect(summingInt(Dish::getCalories));
        menu.stream().mapToInt(Dish::getCalories).sum();
        menu.stream().map(Dish::getCalories).reduce(Integer::sum).get();

        menu.stream().collect(averagingInt(Dish::getCalories));

上述方法一次只能返回一個匯總值如孝,可以通過一次summarizing操作獲得元數(shù)個數(shù)宪哩,總數(shù),平均值第晰,最大值和最小值锁孟,

        IntSummaryStatistics statistic = menu.stream().collect(summarizingInt(Dish::getCalories));
        System.out.println(statistic);

輸出如下:

IntSummaryStatistics{count=9, sum=4470, min=120, average=496.666667, max=800}

1.3 連接字符串

        menu.stream().map(Dish::getName).collect(joining());

        //字符拼接時增加逗號分隔
        menu.stream().map(Dish::getName).collect(joining(", "));

輸出如下

pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon

1.4 廣義的規(guī)約匯總

上述收集器都是reducing工廠方法定義的規(guī)約過程的特殊情況。reducing工廠方法是所有這些特殊情況的一般化茁瘦。

        int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, (i, j)->i+j));
        System.out.println(totalCalories);

三個參數(shù):

  • 規(guī)約操作的起始值
  • 轉(zhuǎn)換函數(shù)
  • BinaryOperator操作

reducing也有單參數(shù)形式品抽,如求最大值:

        Optional<Dish> d = menu.stream().collect(reducing((d1,d2) -> d1.getCalories()>d2.getCalories()? d1:d2));
        System.out.println(d.get());

收集和規(guī)約的區(qū)別:

  • 一個語義問題: reduce方法旨在把兩個值結(jié)合起來生成一個新值,它是一個不可變的規(guī)約腹躁。collect方法設計是要改變?nèi)萜鳎瑥亩鄯e要輸出的結(jié)果南蓬。
  • 一個實際問題: collect更適合并行操作纺非。

2. 分組

按dish的類型type分組:

        menu.stream().collect(groupingBy(Dish::getType));

按自定義熱量分組:

        enum CaloricLevel {DIET, NORMAL, FAT};
        menu.stream().collect(groupingBy(dish->{
            if(dish.getCalories()<400) {
                return CaloricLevel.DIET;
            }else if(dish.getCalories()<700) {
                return CaloricLevel.NORMAL;
            }
            return CaloricLevel.FAT;
        }));

2.1 操作元素分組

使用一個映射函數(shù)對元素進行轉(zhuǎn)換。

        System.out.println(menu.stream().collect(groupingBy(Dish::getType, mapping(Dish::getName, toList()))));

2.2 多級分組

二級分組赘方,先按類型Type分組烧颖,在同類型中,再按熱量值分組窄陡。 相當于二維表格炕淮。

System.out.println(menu.stream().collect(groupingBy(Dish::getType, groupingBy(dish->{
            if(dish.getCalories()<400) {
                return CaloricLevel.DIET;
            }else if(dish.getCalories()<700) {
                return CaloricLevel.NORMAL;
            }else {
                return CaloricLevel.FAT;
            }
        }, mapping(Dish::getName, toList())))));
{FISH={DIET=[prawns], NORMAL=[salmon]}, MEAT={FAT=[pork, beef], NORMAL=[chicken]}, OTHER={FAT=[french fries], DIET=[rice, season fruit], NORMAL=[pizza]}}

2.3 按子組收集數(shù)據(jù)

傳遞給第一個groupingBy的第二個收集器可以是任何類型的,而不一定是groupingBy跳夭。

        System.out.println(menu.stream().collect(groupingBy(Dish::getType, counting())));

結(jié)果:

{FISH=2, MEAT=3, OTHER=4}

按照菜的類型分類涂圆,找到該類型中熱量最高的Dish

    Map<Dish.Type, Optional<Dish>> mostCaloricByType = menu.stream().collect(groupingBy(Dish::getType, maxBy(Comparator.comparingInt(Dish::getCalories))));
        System.out.println(mostCaloricByType);

按照菜的類型分類,并求出每類的熱量總和

    Map<Dish.Type, Integer> totalCaloriesByType = menu.stream().collect(groupingBy(Dish::getType, summingInt(Dish::getCalories)));
    System.out.println(totalCaloriesByType);

按照菜的類型分類币叹,并求出每類的熱量類型集合润歉。
本例用到的是另一個收集器mapping方法。這個方法接收兩個參數(shù)颈抚,一個函數(shù)對流中的元素做變換踩衩,另一個則將變換的結(jié)果對象收集起來。

        Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType = menu.stream().collect(groupingBy(Dish::getType, mapping(dish->{
            if(dish.getCalories()<400) {
                return CaloricLevel.DIET;
            }else if(dish.getCalories()<700) {
                return CaloricLevel.NORMAL;
            }else {
                return CaloricLevel.FAT;
            }
        }, toSet())));
        System.out.println(caloricLevelsByType);

輸出如下:

{FISH=[DIET, NORMAL], MEAT=[FAT, NORMAL], OTHER=[FAT, DIET, NORMAL]}

通過使用toCollection贩汉,可以有更多的控制驱富,同上例子,收集到hashset

        Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType = menu.stream().collect(groupingBy(Dish::getType, mapping(dish->{
            if(dish.getCalories()<400) {
                return CaloricLevel.DIET;
            }else if(dish.getCalories()<700) {
                return CaloricLevel.NORMAL;
            }else {
                return CaloricLevel.FAT;
            }
        }, toCollection(HashSet::new))));
        System.out.println(caloricLevelsByType);

3. 分區(qū)

分區(qū)是分組的特殊情況:由一個謂詞(返回一個布爾值的函數(shù))作為分類函數(shù)匹舞,即分區(qū)函數(shù)褐鸥。最多分兩組,true一組赐稽,false一組晶疼。

按是否是素食進行分類酒贬,并輸出素食列表。

    Map<Boolean, List<Dish>> partitionedMenu = menu.stream().collect(partitioningBy(Dish::isVegetarian));
    System.out.println(partitionedMenu.get(true));

按是否素食進行分類翠霍,在子類中再按照類型進行分類匯總

Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType = menu.stream().collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));
        System.out.println(vegetarianDishesByType);

找到素食和非素食中熱量最高的菜

    Map<Boolean, Dish> mostCaloricPartitionedByVegetarian =  menu.stream().collect(partitioningBy(Dish::isVegetarian, collectingAndThen(maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)));
    System.out.println(mostCaloricPartitionedByVegetarian);

計算每個分區(qū)中項目的數(shù)目

        System.out.println(menu.stream().collect(partitioningBy(Dish::isVegetarian, counting())));
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锭吨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子寒匙,更是在濱河造成了極大的恐慌零如,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锄弱,死亡現(xiàn)場離奇詭異考蕾,居然都是意外死亡,警方通過查閱死者的電腦和手機会宪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門肖卧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掸鹅,你說我怎么就攤上這事塞帐。” “怎么了巍沙?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵葵姥,是天一觀的道長。 經(jīng)常有香客問我句携,道長榔幸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任矮嫉,我火速辦了婚禮削咆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蠢笋。我一直安慰自己态辛,他們只是感情好,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布挺尿。 她就那樣靜靜地躺著奏黑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪编矾。 梳的紋絲不亂的頭發(fā)上熟史,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音窄俏,去河邊找鬼蹂匹。 笑死,一個胖子當著我的面吹牛凹蜈,可吹牛的內(nèi)容都是我干的限寞。 我是一名探鬼主播忍啸,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼履植!你這毒婦竟也來了计雌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤玫霎,失蹤者是張志新(化名)和其女友劉穎凿滤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庶近,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡翁脆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鼻种。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片反番。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖叉钥,靈堂內(nèi)的尸體忽然破棺而出罢缸,到底是詐尸還是另有隱情,我是刑警寧澤沼侣,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布祖能,位于F島的核電站歉秫,受9級特大地震影響蛾洛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雁芙,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一轧膘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兔甘,春花似錦谎碍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至澡匪,卻和暖如春熔任,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背唁情。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工疑苔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人甸鸟。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓惦费,卻偏偏與公主長得像兵迅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子薪贫,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355