用流收集数据
- 用流收集数据 、
- 用Collectors类创建和使用收集器
- 将数据流归约为一个值
- 汇总:归约的特殊情况
- 数据分组和分区
- 开发自己的自定义收集器
使用groupingBy分组、以及max、min、sum
public void printlnTest(){
/**
* 函数式编程相对于指令式编程的一个主要优势:你只需指出希望的
* 结果——“做什么”,而不用操心执行的步骤——“如何做”。
*/
ArrayList<User> userList = Lists.newArrayList();
userList.add(User.builder().sex("女").name("小红").build());
userList.add(User.builder().sex("女").name("小花").build());
userList.add(User.builder().sex("男").name("小张").build());
userList.add(User.builder().sex("男").name("小网").build());
userList.add(User.builder().sex("男").name("小里").build());
// 使用 groupingBy() 按男女性别分组
Map<String, List<User>> sexGroup = userList.stream().collect(groupingBy(User::getSex));
// 打印: sexGroup ==>
// {女=[User(name=小红, sex=女, ...),User(name=小花, sex=女, ...)],
// 男=[User(name=小张,sex=男, ...),User(name=小网, sex=男, ...),User(name=小里, sex=男, ...)]}
System.out.println("sexGroup ==> " + sexGroup);
// 统计分组的个数
Map<String, Long> groupCount = userList.stream().collect(groupingBy(User::getSex, counting()));
// groupCount ==> {女=2, 男=3}
System.out.println("groupCount ==> " + groupCount.toString());
/**
* Collectors实用类提供了很多静态工厂方法,
* 可以方便地创建常见收集器的实例,只要拿来用就可以了。最直接和最常用的收集器是toList
* 静态方法,它会把流中所有的元素收集到一个List中:
*/
List<User> users = userList.stream().collect(Collectors.toList());
/**
* Collectors
* 类提供的工厂方法(例如groupingBy)创建的收集器。它们主要提供了三大功能:
* 将流元素归约和汇总为一个值
* 元素分组
* 元素分区
* 我们先来看看可以进行归约和汇总的收集器。
*/
// 利用counting工厂方法返回的收集器,看有几个人
// counting收集器在和其他收集器联合使用的时候特别有用,后面会谈到这一点
long peopleCount = userList.stream().collect(Collectors.counting());
// 或者 userList.stream().count(); userList.size();
// Java Stream Collectors的maxBy() minBy()、groupingBy()、partitioningBy()的使用
// userList.stream().map(user -> user.getUId()).collect(Collectors.maxBy(Comparator.naturalOrder()));
Optional<Integer> comparatorMax = userList.stream().map(User::getUId).max(Comparator.naturalOrder());
System.out.println("stream() comparatorMax :" + comparatorMax.orElse(0));
// minBy()
Optional<Integer> comparatorMin = userList.stream().map(User::getUId).min(Comparator.naturalOrder());
System.out.println("stream() comparatorMin :" + comparatorMax.orElse(0));
// summingInt 求和
// userList.stream().collect(summingInt(User::getUId))
int summingInt = userList.stream().mapToInt(User::getUId).sum();
}
对groupingBy的理解图:
groupingBy联合使用的其他收集器的例子,一般来说,通过groupingBy工厂方法的第二个参数传递的收集器将会对分到同一组中的所 有流元素执行进一步归约操作。例如,你还重用求出所有菜肴热量总和的收集器,不过这次是对 每一组Dish求和:
Map<Dish.Type, Integer> totalCaloriesByType = menu.stream()
.collect(groupingBy(Dish::getType,summingInt(Dish::getCalories)));
然而常常和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() )));
使用partitioningBy、collectingAndThen
public void demo(){
ArrayList<User> userList = Lists.newArrayList();
userList.add(User.builder().sex("女").name("小红").type(true).uId(10).build());
userList.add(User.builder().sex("女").name("小花").type(false).uId(11).build());
userList.add(User.builder().sex("男").name("小张").type(true).uId(12).build());
userList.add(User.builder().sex("男").name("小网").type(false).uId(13).build());
userList.add(User.builder().sex("男").name("小里").type(true).uId(14).build());
/**
* 先根据类型分组、再根据性别分组
*/
Map<Boolean, Map<String, List<User>>> collect = userList.stream().collect(partitioningBy(User::isType, groupingBy(User::getSex)));
// {false={
// 女=[
// User(uId=null, name=小花, email=null, sex=女, type=false)
// ],
// 男=[
// User(uId=null, name=小网, email=null, sex=男, type=false)
// ]},
//
// true={
// 女=[
// User(uId=null, name=小红, email=null, sex=女, type=true)
// ],
// 男=[
// User(uId=null, name=小张, email=null, sex=男, type=true),
// User(uId=null, name=小里, email=null, sex=男, type=true)
// ]}
// }
System.out.println(collect.toString());
// 查询true和false中两个类型中,uid最大的对象
Map<Boolean, User> collectType = userList.stream().collect(partitioningBy(User::isType, collectingAndThen(maxBy(comparingInt(User::getUId)), Optional::get)));
// {
// false = User(uId=13, name=小网, sex=男, type=false),
// true = User(uId=14, name=小里, sex=男, type=true)
// }
System.out.println(collectType.toString());
}
Collector收集器的介绍及使用
一个Collector收集器由四个函数指定,这四个函数一起工作以将条目累积到可变结果容器中,并可选择对结果执行最终转换。他们是:
- 创建新的结果容器(supplier())
- 将新数据元素合并到结果容器中(accumulator())
- 将两个结果容器合并为一个(combiner())
- 在容器上执行可选的最终转换(finisher())
收集器还具有一组特征,例如Collector.Characteristics.CONCURRENT,提供了可以缩减实现使用,以提供更好性能的提示。
使用收集器缩减的顺序实现将使用supplier函数创建单个结果容器,并为每个输入元素调用一次accumulator累加器函数。并行实现将对输入进行分区,为每个分区创建一个结果容器,将每个分区的内容累积到该分区的子结果中,然后使用combiner组合器函数将子结果合并为组合结果。为确保顺序和并行执行产生相同的结果,收集器函数必须满足标识和关联约束。
第六章结尾:开发你自己的收集器以获得更好的性能【略】