开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第27天,点击查看活动详情
流可以用类似于数据库的操作帮助我们处理集合,Java8的流它支持两种类型的操作,中间操作和终端操作。中间操作可以链接起来,将一个流转换为另一个流。这些操作不会消耗流,其目的是建立一个流水线。与此相反,终端操作会消耗流,以产生一个最终结果。
1、归约和汇总
数一数菜单里有多少种菜
long howManyDishes = menu.stream().collect(Collectors.counting());
long howManyDishes = menu.stream().count();
热量最高的菜
Optional<Dish> mostCaloriesDish = menu.stream()
.collect(maxBy(Comparator.comparingInt(Dish::getCalories)));
菜单总热量
int totalCalories = menu.stream().collect(Collectors.summingInt(Dis::getCalories));
菜单平均热量
double avgCalories = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
通过一次summarizing操作可以计算出菜单中元素个数、热量总和、平均值、最大值、最小值
IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
这个收集器会把所有这个信息收集到一个叫做IntSummaryStatistics的类里,它提供了方便的取值方法来访问结果。打印menuStatistics得到如下结果:
IntSummaryStatistics{count=9, sum=4300, min=120, average=477.777778, max=800}
连接字符串
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
// joining在内部使用了StringBuilder来把生成的字符串逐个追加起来。
String showmenu = menu.stream.collect(joining(", "));
广义的归约汇总
前面的所有收集器,都是一个可以用reducing工厂方法定义的归约过程的特殊情况而已。Collectors.reducing工厂方法是所有这些特殊情况的一般化。比如用reducing方法创建的收集器来计算菜单总热量:
int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));
- 第一个参数是归约操作的起始值,也是流中没有元素时的返回值,所以很显然对于数值和而言0是一个合适的值
- 第二个参数,将菜肴转换成一个表示其所含热量的int
- 第三个参数,是一个BinaryOperator,将两个项目累积成一个同类型的值。这里它就是对两个int求和
Optional<Dish> mostCaloriesDish = menu.stream()
.collect(reducing(d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2);
可以把单参数reducing工厂方法创建的收集器看作三参数方法的特殊情况,它把流中的第一个项目作为起点,把恒等函数(即一个函数仅仅是返回其输入参数)作为转换函数。这也意味着,要把单参数reducin收集器传递给空流的collect方法,收集器就没有起点。
2、分组
一个常见的数据库操作是根据一个或多个属性对集合中的项目进行分组。就像按货币对交易进行分组的例子一样。
Map<Dish.Type, List<Dish>> dishesByType = menu.stream()
.collect(Collectors.groupingBy(Dish::getType));
groupingBy方法传递了一个Function,它提取了流中每一道Dish的Dish.Type,我们把这个Funciton叫作分类函数,因为它用来把流中的元素分成不同的组,分组操作的结果是一个Map,把分组函数返回的值作为映射的键,把流中所有具有这个分类值的项目的列表作为对应的映射值。在菜单分类的例子中,键就是菜的类型,值就是饮食所有对应类型的菜肴的列表。
一些更复杂的情况。例如,把热量不到400卡路里的菜划分为低热量diet,热量400-700卡路里的菜划分为普通normal,高于700卡路里的划分为高热量fat,可以这样写
public enum CaloricLevel {DIET, NORMAL, FAG}
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
})
);