日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java 8 - 收集器Collectors_分组groupingBy

發布時間:2025/3/21 java 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 8 - 收集器Collectors_分组groupingBy 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • Pre
  • 多級分組
  • 按子組收集數據
    • 查找每個子組中熱量最高的 Dish
    • 圖解工作過程
    • 與 groupingBy聯合使用的其他收集器的例子


Pre

來看個小例子: 把菜單中的菜按照類型進行分類,有菜的放一組,有肉的放一組,其他的都放另一組。

Map<Dish.Type, List<Dish>> collect = menu.stream().collect(groupingBy(Dish::getType));

用 Collectors.groupingBy 工廠方法返回的收集器就可以輕松地完成這項任務。

這里,給 groupingBy 方法傳遞了一個 Function (以方法引用的形式),它提取了流中每一道 Dish 的 Dish.Type 。我們把這個 Function 叫作分類函數,因為它用來把流中的元素分成不同的組。

如下圖所示,分組操作的結果是一個 Map ,把分組函數返回的值作為映射的鍵,把流中所有具有這個分類值的項目的列表作為對應的映射值。

在菜單分類的例子中,鍵就是菜的類型,值就是包含所有對應類型的菜的列表。


【第二個例子】

但是,分類函數不一定像方法引用那樣可用,因為你想用以分類的條件可能比簡單的屬性訪問器要復雜。

例如,你可能想把熱量不到400卡路里的菜分為“低熱量”(diet),熱量400到700卡路里的菜為“普通”(normal),高于700卡路里的菜為“高熱量”(fat)。

由于 Dish 類 沒有把這個操作寫成一個方法,你無法使用方法引用,但你可以把這個邏輯寫成Lambda表達式:

public enum CaloricLevel { DIET, NORMAL, FAT }Map<CaloricLevel, List<Dish>> collect = menu.stream().collect(groupingBy(dish -> {if (dish.getCalories() > 300) {return CaloricLevel.DIET;} else if (dish.getCalories() <= 700) {return CaloricLevel.NORMAL;} else {return CaloricLevel.FAT;}}));

多級分組

現在,已經看到了如何對菜單中的菜肴按照類型和熱量進行分組,但要是想同時按照這兩個標準分類怎么辦呢?分組的強大之處就在于它可以有效地組合。讓我們來看看怎么做。

要實現多級分組,我們可以使用一個由雙參數版本的 Collectors.groupingBy 工廠方法創建的收集器,它除了普通的分類函數之外,還可以接受 collector 類型的第二個參數。那么要進行二級分組的話,我們可以把一個內層 groupingBy 傳遞給外層 groupingBy ,并定義一個為流中項目分類的二級標準。

public static Map<Dish.Type, Map<CaloricLevel, List<Dish>>> duobleGroup(){Map<Dish.Type, Map<CaloricLevel, List<Dish>>> collect = 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;}})));System.out.println(collect);return collect;}

我們把Dish的toString方法改寫一下

@Overridepublic String toString() {return name;}

輸出

{MEAT={FAT=[pork], DIET=[chicken], NORMAL=[beef]}, OTHER={DIET=[rice, season fruit], NORMAL=[french fries, pizza]}, FISH={DIET=[prawns], NORMAL=[salmon]}}

輸出的結果里的外層 Map 的鍵就是第一級分類函數生成的值:“fish, meat, other”, 而這個 Map 的值又是一個 Map ,鍵是二級分類函數生成的值:“normal, diet, fat”。最后,第二級 map 的值是流中元素構成的 List ,是分別應用第一級和第二級分類函數所得到的對應第一級和第二級鍵的值:“salmon、pizza…” 這種多級分組操作可以擴展至任意層級,n級分組就會得到一個代表n級樹形結構的n級Map 。

一般來說,把 groupingBy 看作“桶”比較容易明白。第一個 groupingBy 給每個鍵建立了一個桶。然后再用下游的收集器去收集每個桶中的元素,以此得到n級分組。


按子組收集數據

上個例子中,我們看到可以把第二個 groupingBy 收集器傳遞給外層收集器來實現多級分組。但進一步說,傳遞給第一個 groupingBy 的第二個收集器可以是任何類型,而不一定是另一個 groupingBy 。

例如,要數一數菜單中每類菜有多少個,可以傳遞 counting 收集器作為groupingBy 收集器的第二個參數

menu.stream().collect(groupingBy(Dish::getType, counting()))

輸出

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

還要注意,普通的單參數 groupingBy(f) (其中 f 是分類函數)實際上是 groupingBy(f,toList()) 的簡便寫法。

再舉一個例子,你可以把前面用于查找菜單中熱量最高的菜的收集器改一改,按照菜的類型分類:

// 按類型分類System.out.println(menu.stream().collect(groupingBy(Dish::getType)));// 按類型分類,獲取最高熱量的菜System.out.println(menu.stream().collect(groupingBy(Dish::getType, maxBy(Comparator.comparing(Dish::getCalories)))));

輸出

{FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza], MEAT=[pork, beef, chicken]} {FISH=Optional[salmon], OTHER=Optional[pizza], MEAT=Optional[pork]}

這個 Map 中的值是 Optional ,因為這是 maxBy 工廠方法生成的收集器的類型,但實際上,如果菜單中沒有某一類型的 Dish ,這個類型就不會對應一個 Optional. empty() 值,而且根本不會出現在 Map 的?中。 groupingBy 收集器只有在應用分組條件后,第一次在流中找到某個鍵對應的元素時才會把鍵加入到分組 Map 中。這意味著 Optional 包裝器在這里不是很有用,因為它不會僅僅因為它是歸約收集器的返回類型而表達一個最終可能不存在卻意外存在的值。

【把收集器的結果轉換為另一類型】

因為分組操作的 Map 結果中的每個值上包裝的 Optional 沒什么用,所以你可能想要把它們去掉。要做到這一點,或者更一般地來說,把收集器返回的結果轉換為另一種類型,你可以使用Collectors.collectingAndThen 工廠方法返回的收集器

查找每個子組中熱量最高的 Dish

// 按類型分類,獲取最高熱量的菜System.out.println(menu.stream().collect(groupingBy(Dish::getType,collectingAndThen(maxBy(Comparator.comparing(Dish::getCalories)),Optional::get))));

輸出

{FISH=salmon, OTHER=pizza, MEAT=pork}

這個工廠方法接受兩個參數——要轉換的收集器以及轉換函數,并返回另一個收集器。這個收集器相當于舊收集器的一個包裝, collect 操作的最后一步就是將返回值用轉換函數做一個映射。在這里,被包起來的收集器就是用 maxBy 建立的那個,而轉換函數 Optional::get 則把返回的 Optional 中的值提取出來。

這個操作放在這里是安全的,因為 reducing收集器永遠都不會返回 Optional.empty() 。


圖解工作過程

從最外層開始逐層向里,注意以下幾點

  • 收集器用虛線表示,因此 groupingBy 是最外層,根據菜肴的類型把菜單流分組,得到三個子流
  • groupingBy 收集器包裹著 collectingAndThen 收集器,因此分組操作得到的每個子流都用這第二個收集器做進一步歸約
  • collectingAndThen 收集器又包裹著第三個收集器 maxBy
  • 隨后由歸約收集器進行子流的歸約操作,然后包含它的 collectingAndThen 收集器會對其結果應用 Optional:get 轉換函數。
  • 對三個子流分別執行這一過程并轉換而得到的三個值,也就是各個類型中熱量最高的Dish ,將成為 groupingBy 收集器返回的 Map 中與各個分類鍵( Dish 的類型)相關聯的值。

與 groupingBy聯合使用的其他收集器的例子

一般來說,通過 groupingBy 工廠方法的第二個參數傳遞的收集器將會對分到同一組中的所有流元素執行進一步歸約操作。

例如,你還重用求出所有菜肴熱量總和的收集器,不過這次是對每一組 Dish 求和

menu.stream().collect(groupingBy(Dish::getType,summingInt(Dish::getCalories)));

返回

{MEAT=1900, FISH=750, OTHER=1550}

然而常常和 groupingBy 聯合使用的另一個收集器是 mapping 方法生成的。這個方法接受兩個參數:

  • 一個函數對流中的元素做變換
  • 另一個則將變換的結果對象收集起來

其目的是在累加之前對每個輸入元素應用一個映射函數,這樣就可以讓接受特定類型元素的收集器適應不同類型的對象。

比方說你想要知道,對于每種類型的 Dish 菜單中都有哪些 CaloricLevel 。我們可以把 groupingBy 和 mapping 收集器結合起來,如下所示:

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

輸出:

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

傳遞給映?方法的轉換函數將 Dish 映射成了它的CaloricLevel :生成的 CaloricLevel 流傳遞給一個 toSet 收集器,它和 toList 類似,不過是把流中的元素映射到一個 Set 而不是 List 中,以便僅保留各不相同的值。

請注意在上一個示例中,對于返回的 Set 是什么類型并沒有任何保證。但通過使用 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);

輸出

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

public static 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, 530, 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));

總結

以上是生活随笔為你收集整理的Java 8 - 收集器Collectors_分组groupingBy的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 午夜久久久久久久久久久 | 国产乱子伦一区二区 | 日韩精品一区二区三区中文在线 | 美女网站免费观看视频 | 黄色一级一片免费播放 | 日本 奴役 捆绑 受虐狂xxxx | 精品人体无码一区二区三区 | 黄视频免费观看 | 日韩一二三级 | 另类男人与善交video | 黄色大片儿. | 深夜激情网站 | 久久92| 亚洲人交配 | 日本不卡二区 | 亚洲不卡视频在线观看 | 思思久久久 | 大陆av在线播放 | 国产91精 | 国产精品成人一区二区三区电影毛片 | 日本福利视频导航 | 精品国产乱码一区二 | 亚洲永久免费精品 | 日韩欧美高清在线视频 | 日韩一区不卡视频 | 久久激情影院 | 国产a v一区二区三区 | 国产乱人视频 | 亚洲一区二区三区四区在线观看 | 欧美日本在线看 | 免费黄网站在线 | 国产又色又爽又黄又免费 | 日韩一区不卡视频 | 本站只有精品 | 日本免费无人高清 | 精品自拍一区 | 搞黄视频在线观看 | 国产中年熟女高潮大集合 | 欧美久久网| 亚洲精品久久久久久动漫器材一区 | 亚洲天堂网络 | 国产激情视频在线观看 | 台湾色综合 | 日日夜夜综合 | 午夜精品久久久久久久四虎美女版 | 日本美女一区二区三区 | 啦啦啦av| 成人天堂噜噜噜 | 手机在线看片福利 | 91精品视频网 | 国产真实生活伦对白 | 中文在线视频观看 | 日本三级大全 | 久久精品a亚洲国产v高清不卡 | 2019中文字幕在线视频 | 日本高清不卡码 | 欧美色图激情小说 | 成人免费黄色网址 | 伊人影院中文字幕 | 欧美日本在线视频 | 国产免费一区二区 | 在线播放小视频 | 亚洲熟妇无码另类久久久 | 四虎黄色网址 | 黄色亚洲精品 | 亚洲 欧美 日韩系列 | 操夜夜操 | 性奶老妇 视频 | 日韩高清毛片 | 国产农村av| 91色偷偷 | ts人妖在线观看 | 欧美日韩电影一区二区三区 | 久久亚洲AV成人无码国产野外 | 中文字幕在线精品 | 国产农村妇女精品久久久 | 久久精品国产大片免费观看 | 欧美视频在线观看一区二区 | 99视频热| 91高清国产| 人乳喂奶hd无中字 | 国产黄色免费在线观看 | 91在线免费播放 | 午夜久久久 | 天天想你在线观看完整版电影免费 | 最近的中文字幕 | 国产精品无码一区二区无人区多人 | 国产麻豆精品一区 | 久草麻豆 | 欧美特级黄色录像 | 中文字幕一二区 | 激情欧美日韩 | 女儿朋友 | 国产999在线观看 | 九色porny自拍| 蜜桃av影院 | 国产91精品欧美 | 欧美国产日韩视频 | 亚洲熟妇无码av |