收集器可以簡潔而靈活地定義collect用來生成結(jié)果集合的標(biāo)準(zhǔn)。更具體地說,對流調(diào)用 collect 方法將對流中的元素觸發(fā)一個(gè)歸約操作(由Collector來參數(shù)化)。一般來說,Collector 會對元素應(yīng)用一個(gè)轉(zhuǎn)換函數(shù)(很多時(shí)候是不體現(xiàn)任何效果的恒等轉(zhuǎn)換, 例如 toList ),并將結(jié)果累積在一個(gè)數(shù)據(jù)結(jié)構(gòu)中,從而產(chǎn)生這一過程的最終輸出。下面就來學(xué)習(xí)那些可以從Collectors 類提供的工廠方法(例如groupingBy)創(chuàng)建的收集器。
歸約和匯總
查找流中的最大值和最小值
Collectors.maxBy 和 Collectors.minBy 來計(jì)算流中的最大或最小值。
Optional<Dish> maxDish = Dish.menu.stream().
collect(Collectors.maxBy(Comparator.comparing(Dish::getCalories)));
maxDish.ifPresent(System.out::println);
Optional<Dish> minDish = Dish.menu.stream().
collect(Collectors.minBy(Comparator.comparing(Dish::getCalories)));
minDish.ifPresent(System.out::println);
匯總
Collectors.summingInt 匯總求和;
Collectors.averagingInt 匯總求平均值;
Collectors.summarizingInt 匯總所有信息包括數(shù)量、求和、平均值、最小值、最大值;
//求總熱量
int totalColories = Dish.menu.stream().collect(Collectors.summingInt(Dish::getCalories));
System.out.println(totalColories);
//求平均熱量
double averageColories = Dish.menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
System.out.println(averageColories);
//匯總
IntSummaryStatistics menuStatistics = Dish.menu.stream().collect(Collectors.summarizingInt(Dish::getCalories));
System.out.println(menuStatistics);
IntSummaryStatistics{count=9, sum=4300, min=120, average=477.777778, max=800}
連接字符串
joining 工廠方法返回的收集器會把對流中每一個(gè)對象應(yīng)用toString方法得到的所有字符串連接成一個(gè)字符串。
String menu = Dish.menu.stream().map(Dish::getName).collect(Collectors.joining(","));
System.out.println(menu);
//pork,beef,chicken,french fries,rice,season fruit,pizza,prawns,salmon
Collectors.reducing
Collectors.reducing 工廠方法是上面所有工廠方法的一般情況,它完全可以實(shí)現(xiàn)上述方法的功能。它需要三個(gè)參數(shù):
- 第一個(gè)參數(shù)是歸約操作的起始值,也是流中沒有元素時(shí)的返回值,所以很顯然對于數(shù)值和而言0是一個(gè)合適的值。
- 第二個(gè)參數(shù)是一個(gè) Function,就是具體的取值函數(shù)。
- 第三個(gè)參數(shù)是一個(gè) BinaryOperator,將兩個(gè)項(xiàng)目累積成一個(gè)同類型的值。。
int totalCalories = Dish.menu.stream().collect(Collectors.reducing( 0, Dish::getCalories, (i, j) -> i + j));
分組
用Collectors.groupingBy工廠方法返回的收集器可以實(shí)現(xiàn)分組任務(wù),分組操作的結(jié)果是一個(gè)Map,把分組函數(shù)返回的值作為映射的鍵,把流中 所有具有這個(gè)分類值的項(xiàng)目的列表作為對應(yīng)的映射值。
多級分組
//Dish的Type為鍵,Dish類型所對應(yīng)的dish集合為值
Map<Dish.Type, List<Dish>> dishesByType = Dish.menu.stream().collect(Collectors.groupingBy(Dish::getType));
System.out.println(dishesByType);
//{FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza], MEAT=[pork, beef, chicken]}
//一級分類為Dish的Type,二級分類為Dish的CaloricLevel
Map<Dish.Type, Map<Dish.CaloricLevel, List<Dish>>> dishes = Dish.menu.stream()
.collect(Collectors.groupingBy(Dish::getType, Collectors.groupingBy(Dish::getLevel)));
System.out.println(dishes);
//{FISH={NORMAL=[salmon], DIET=[prawns]}, OTHER={NORMAL=[french fries, pizza], DIET=[rice, season fruit]}, MEAT={NORMAL=[beef], FAT=[pork], DIET=[chicken]}}
按子集收集數(shù)據(jù)
//Dish的Type為鍵,Dish類型所對應(yīng)的dish集合的size為值
Map<Dish.Type, Long> dishTypeCount = Dish.menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.counting()));
System.out.println(dishTypeCount);
//{FISH=2, OTHER=4, MEAT=3}
分區(qū)
分區(qū)是分組的特殊情況:由一個(gè)謂詞(返回一個(gè)布爾值的函數(shù))作為分類函數(shù),它稱分區(qū)函數(shù)。分區(qū)函數(shù)返回一個(gè)布爾值,這意味著得到的分組 Map 的鍵類型是 Boolean,于是它最多可以分為兩組——true是一組,false是一組。分區(qū)的好處在于保留了分區(qū)函數(shù)返回true或false的兩套流元素列表。
Map<Boolean, Map<Dish.Type, List<Dish>>> partitioningDish = Dish.menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian, Collectors.groupingBy(Dish::getType)));
System.out.println(partitioningDish);
//false={FISH=[prawns, salmon], MEAT=[pork, beef, chicken]},
//true={OTHER=[french fries, rice, season fruit, pizza]}
小結(jié)
下表展示 Collectors 類的靜態(tài)工廠方法。
| 工廠方法 | 返回類型 | 作用 |
|---|---|---|
| toList | List<T> |
把流中所有項(xiàng)目收集到一個(gè) List |
| toSet | Set<T> |
把流中所有項(xiàng)目收集到一個(gè) Set,刪除重復(fù)項(xiàng) |
| toCollection | Collection<T> |
把流中所有項(xiàng)目收集到給定的供應(yīng)源創(chuàng)建的集合menuStream.collect(toCollection(), ArrayList::new)
|
| counting | Long | 計(jì)算流中元素的個(gè)數(shù) |
| sumInt | Integer | 對流中項(xiàng)目的一個(gè)整數(shù)屬性求和 |
| averagingInt | Double | 計(jì)算流中項(xiàng)目 Integer 屬性的平均值 |
| summarizingInt | IntSummaryStatistics | 收集關(guān)于流中項(xiàng)目 Integer 屬性的統(tǒng)計(jì)值,例如最大、最小、 總和與平均值 |
| joining | String | 連接對流中每個(gè)項(xiàng)目調(diào)用 toString 方法所生成的字符串collect(joining(", "))
|
| maxBy | Optional<T> |
一個(gè)包裹了流中按照給定比較器選出的最大元素的 Optional, 或如果流為空則為 Optional.empty() |
| minBy | Optional<T> |
一個(gè)包裹了流中按照給定比較器選出的最小元素的 Optional, 或如果流為空則為 Optional.empty() |
| reducing | 歸約操作產(chǎn)生的類型 | 從一個(gè)作為累加器的初始值開始,利用 BinaryOperator 與流 中的元素逐個(gè)結(jié)合,從而將流歸約為單個(gè)值累加int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));
|
| collectingAndThen | 轉(zhuǎn)換函數(shù)返回的類型 | 包裹另一個(gè)收集器,對其結(jié)果應(yīng)用轉(zhuǎn)換函數(shù)int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size))
|
| groupingBy | Map<K, List<T>> |
根據(jù)項(xiàng)目的一個(gè)屬性的值對流中的項(xiàng)目作問組,并將屬性值作 為結(jié)果 Map 的鍵 |
| partitioningBy | Map<Boolean,List<T>> |
根據(jù)對流中每個(gè)項(xiàng)目應(yīng)用謂詞的結(jié)果來對項(xiàng)目進(jìn)行分區(qū) |
附錄:Dish類
package com.company.bean;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* Created by liuguoquan on 2017/4/26.
*/
public class Dish {
private String name;
private boolean vegetarian;
private int calories;
private Type type;
private CaloricLevel level;
public CaloricLevel getLevel() {
if (calories <= 400) {
return CaloricLevel.DIET;
} else if (calories <= 700) {
return CaloricLevel.NORMAL;
}
return CaloricLevel.FAT;
}
public void setLevel(CaloricLevel level) {
this.level = level;
}
public enum Type { MEAT, FISH, OTHER }
public enum CaloricLevel { DIET, NORMAL, FAT }
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
@Override
public String toString() {
return name;
}
public static final 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, 400, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
}