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