【Java8实战】使用流收集数据
在上一节中,我们了解到终端操作collect方法用于收集流中的元素,并放到不同类型的结果中,比如List、Set或者Map。其实collect方法可以接受各种Collectors接口的静态方法作为参数来实现更为强大的规约操作,比如查找最大值最小值,汇总,分区和分组等等。 准备工作为了演示Collectors接口中的静态方法使用,这里创建一个Dish类(菜谱类): public class Dish {
? 然后创建一个List,包含各种食材: List<Dish> list = Arrays.asList(
? 在测试类中导入所有Collectors接口的静态方法: import static java.util.stream.Collectors.*;
? 规约与汇总最大最小值Collectors.maxBy和Collectors.minBy用来计算流中的最大或最小值,比如按卡路里的大小来筛选出卡路里最高的食材: list.stream()
? 输出pork。 汇总Collectors.summingInt可以用于求和,参数类型为int类型。相应的基本类型对应的方法还有Collectors.summingLong和Collectors.summingDouble。比如求所有食材的卡路里: list.stream().collect(summingInt(Dish::getCalories)); // 4200
? Collectors.averagingInt方法用于求平均值,参数类型为int类型。相应的基本类型对应的方法还有Collectors.averagingLong和Collectors.averagingDouble。比如求所有食材的平均卡路里: list.stream().collect(averagingInt(Dish::getCalories)); // 466.6666666666667
? Collectors.summarizingInt方法可以一次性返回元素个数,最大值,最小值,平均值和总和: IntSummaryStatistics iss = list.stream().collect(summarizingInt(Dish::getCalories));
? 同样,相应的summarizingLong和summarizingDouble方法有相关的LongSummaryStatistics和DoubleSummaryStatistics类型,适用于收集的属性是原始类型long或double的情况。 拼接Collectors.joining方法会把流中每一个对象应用toString方法得到的所有字符串连接成一个字符串。如: list.stream().map(Dish::getName).collect(joining());
? 内部拼接采用了StringBuilder。除此之外,也可以指定拼接符: list.stream().map(Dish::getName).collect(joining(","));
? reducingCollectors.reducing方法可以实现求和,最大值最小值筛选,拼接等操作。上面介绍的方法在编程上更方便快捷,但reducing的可读性更高,实际使用哪种我觉得还是看个人喜好。举个使用reducing求最大值的例子: list.stream().collect(reducing(0,Dish::getCalories,Integer::max)); // 800
? 或者: list.stream().map(Dish::getCalories).collect(reducing(0,Integer::max)); // 800
? 分组分组功能类似于SQL里的group by,可以对流中的元素按照指定分组规则进行分组。 普通分组Collectors.groupingBy方法可以轻松的完成分组操作。比如现在对List中的食材按照类型进行分组: Map<Dish.Type,List<Dish>> dishesByType = list.stream().collect(groupingBy(Dish::getType));
? 输出结果{OTHER=[french fries,rice,season fruit,pizza],FISH=[prawns,salmon],MEAT=[pork,beef,chicken]}。 我们也可以自定义分组规则,比如按照卡路里的高低分为高热量,正常和低热量: 首先定义一个卡路里高低的枚举类型 public enum CaloricLevel { DIET,NORMAL,FAT };
? 然后编写分组规则: Map<CaloricLevel,List<Dish>> dishesByCalories = list.stream().collect(
? 输出结果:{DIET=[chicken,prawns],NORMAL=[beef,french fries,pizza,FAT=[pork]}。 多级分组Collectors.groupingBy支持嵌套实现多级分组,比如将食材按照类型分类,然后再按照卡路里的高低分类: Map<Dish.Type,Map<CaloricLevel,List<Dish>>> dishesGroup = list.stream().collect(
? 返回结果是一个二级Map,输出结果{FISH={DIET=[prawns],NORMAL=[salmon]},OTHER={DIET=[rice,season fruit],NORMAL=[french fries,pizza]},MEAT={DIET=[chicken],FAT=[pork],NORMAL=[beef]}}。 实际上,第二个参数除了Collectors.groupingBy外,也可以传递其他规约操作,规约的结果类型对应Map里的第二个泛型。举些例子,将食材按照类型分,然后统计各个类型对应的数量: Map<Dish.Type,Long> dishesCountByType = list.stream().collect(groupingBy(Dish::getType,counting()));
? 因为Collectors.counting方法返回Long类型,所以Map第二个泛型也必须指定为Long。输出结果:{OTHER=4,FISH=2,MEAT=3}。 或者对食材按照类型分,然后选出卡路里最高的食物: Map<Dish.Type,Optional<Dish>> map = list.stream().collect(groupingBy(
? 输出结果:{OTHER=Optional[pizza],MEAT=Optional[pork],FISH=Optional[salmon]}。如果不希望输出结果包含Optional,可以使用Collectors.collectingAndThen方法: Map<Dish.Type,Dish> map = list.stream().collect(groupingBy( Dish::getType,collectingAndThen(maxBy(Comparator.comparing(Dish::getCalories)),Optional::get) )); System.out.println(map); ? 输出结果:{OTHER=pizza,FISH=salmon,MEAT=pork}。 常与Collectors.groupingBy组合使用的方法还有Collectors.mapping。Collectors.mapping方法接受两个参数:一个函数对流中的元素做变换,另一个则将变换的结果对象收集起来,比如对食材按照类型分类,然后输出各种类型食材下卡路里等级情况: Map<Dish.Type,HashSet<CaloricLevel>> map = list.stream().collect(groupingBy(
? Collectors.toCollection方法可以方便的构造各种类型的集合。输出结果:{FISH=[DIET,NORMAL],MEAT=[DIET,FAT],OTHER=[DIET,NORMAL]}。 分区分区类似于分组,只不过分区最多两种结果。Collectors.partitioningBy方法用于分区操作,接收一个Predicate<T>类型的Lambda表达式作为参数。比如将食材按照素食与否分类: Map<Boolean,List<Dish>> map = list.stream().collect(partitioningBy(Dish::isVegetarian));
? 输出结果:{false=[pork,chicken,prawns,true=[french fries,pizza]}。 Collectors.partitioningBy方法还支持传入分组函数或者其他规约操作,比如将食材按照素食与否分类,然后按照食材类型进行分类: Map<Boolean,Map<Dish.Type,List<Dish>>> map = list.stream().collect(
? 输出结果:{false={MEAT=[pork,chicken],salmon]},true={OTHER=[french fries,pizza]}}。 再如将食材按照素食与否分类,然后筛选出各自类型中卡路里含量最低的食材: Map<Boolean,Dish> map = list.stream().collect(
? 输出结果:{false=prawns,true=season fruit}。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |