java8学习:用流收集数据
内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。
书籍购买地址:java8实战
下面我们将采用这样一个实体类
@Data public class Dish {private final String name;private final boolean vegetarian; //是否是素食private final int calories; //卡路里private final Type type; //类型public enum Type{MEAT,FISH,OTHER;}public Dish(String name, boolean vegetarian, int calories, Type type) {this.name = name;this.vegetarian = vegetarian;this.calories = calories;this.type = type;} }
- 上面是一个菜单的一个实体类,下面我们添加到list中
List<Dish> menu = Arrays.asList(new Dish("apple",true,50, Dish.Type.OTHER),new Dish("chicken",false,350, Dish.Type.MEAT),new Dish("rich",true,150, Dish.Type.OTHER),new Dish("pizza",true,350, Dish.Type.OTHER),new Dish("fish",false,250, Dish.Type.FISH),new Dish("orange",true,70, Dish.Type.OTHER),new Dish("banana",true,60, Dish.Type.OTHER));
- 好了到这就可以开始进行菜单中菜的分类了,那么我们可以先按着原来的办法,按着type分类,如下
@Test public void test() throws Exception {Map<Dish.Type,List<Dish>> groupByType = new HashMap<>();for (Dish dish : menu) {Dish.Type type = dish.getType();List<Dish> dishes = groupByType.get(type);if (dishes == null){ //如果为null说明第一次遇到某一个typedishes = new ArrayList<>();groupByType.put(type,dishes);}dishes.add(dish);}System.out.println(groupByType); }
- 那么我们用Stream中的collect方法来实现
@Test public void test() throws Exception {Map<Dish.Type, List<Dish>> collect = menu.stream().collect(Collectors.groupingBy(menu -> menu.getType()));System.out.println("collect = " + collect); }
- 到这我们就知道了collect能给我们带来的便捷,下面将具体介绍collect的使用,以及自定义一个collect能接受的参数
使用
- 求上面菜单中有多少个菜
Long collect = menu.stream().collect(Collectors.counting()); //更好的做法 long count = menu.stream().count();
- 查找最大和最小的卡路里
//既然是找出最大和最小的卡路里,那么肯定是有比较器的,比较器比较Dish中的卡路里属性 //最大 Comparator<Dish> comparator = Comparator.comparing(Dish::getCalories); Optional<Dish> collect = menu.stream().collect(Collectors.maxBy(comparator)); //对于返回Option很正常,万一比较列表没有值呢?所以会返回一个Option容器 //最小 Optional<Dish> collect1 = menu.stream().collect(Collectors.minBy(comparator));
- 汇总:summingInt(),可接收一个把对象映射为求和所需int的函数,并返回收集器,(也就是说求和操作)
//计算卡路里总和 Integer collect = menu.stream().collect(Collectors.summingInt(Dish::getCalories)); System.out.println(collect); //下面的更好一些,避免了拆箱装箱操作 int sum = menu.stream().mapToInt(Dish::getCalories).sum(); System.out.println("sum = " + sum); //当然有summingInt就会有summintLong,summingDouble
- 求卡路里平均数
Double collect = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
- 还有一个方法的返回值包含上面所有的信息:summarizingInt()
IntSummaryStatistics collect = menu.stream().collect(Collectors.summarizingInt(Dish::getCalories)); System.out.println("collect = " + collect); //输出如下,包含最大值最小值等信息 输出:collect = IntSummaryStatistics{count=7, sum=1280, min=50, average=182.857143, max=350} //想访问里面的任一属性只要通过get方法即可,比如:getMin();
- 连接字符串
//将菜名全部连接起来 String collect = menu.stream().map(Dish::getName).collect(Collectors.joining()); //collect = applechickenrichpizzafishorangebanana System.out.println("collect = " + collect); //上面的结果看不清楚,join也提供了重载的方法,可以加入分隔符 String collect1 = menu.stream().map(Dish::getName).collect(Collectors.joining(",")); //collect1 = apple,chicken,rich,pizza,fish,orange,banana System.out.println("collect1 = " + collect1); String collect2 = menu.stream().map(Dish::getName).collect(Collectors.joining(",","[","]")); //collect2 = [apple,chicken,rich,pizza,fish,orange,banana] System.out.println("collect2 = " + collect2);
- ruducing
//我们可以使用reducing方法来实现之前的求和操作 //0作为初始值,Dish::getCalories每次要执行的方法会返回一个值,Integer::sum对返回的值进行的操作//初始值 转换函数 累计函数 Integer collect = menu.stream().collect(Collectors.reducing(0, Dish::getCalories, Integer::sum)); //实现上面求卡路里最大值 //可以将下面的一个参数的reducing看做是上面三个参数的特殊形式,它把流中的第一个项目作为起点,返回值是输入的参数dish1或dish2,所以如果输入参数 //不存在也会有Option Optional<Dish> collect = menu.stream().collect(Collectors.reducing(((dish1, dish2) -> dish1.getCalories() > dish2.getCalories() ? dish1 : dish2)));
我们前面说的reduce和现在说的collect有什么区别?
- reduce方法旨在把两个值结合起来生成一个新值,他是一个不可变的归约
- collect方法的设计就是要改变容器,从而累计要输出的结果
- reducing测试
menu.stream().collect(Collectors.reducing(((dish1, dish2) -> dish1.getName() + dish2.getName()))); //这段代码可以通过编译吗? //不可以因为reducing需要一个BinaryOperator,而它的定义如下 public interface BinaryOperator<T> extends BiFunction<T,T,T> //如此可以看出它传入的TT返回也需要是T类型,所以我们传入Dish返回的也应该是Dish类型
分组
- 其实前面第一个例子我们应使用了分组了,那就是根据菜单中的type进行分组
//我们给groupingBy方法传递了一个FUnction,它提取了流中每一道Dish的type,然后根据type分组,我们把传入的Function称为分类函数,因为他用来把流中的元素分成不同的组 //map的key就是type类型,value就是属于type的所有Dish Map<Dish.Type, List<Dish>> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType));
- 但是如果我们的分类条件并不一定是方法引用的返回值呢?比如我们要卡路里> 120的和小于120的,那该怎么分?
@Test public void test() throws Exception {Map<Integer, List<Dish>> collect = menu.stream().collect(Collectors.groupingBy(dish -> {if (dish.getCalories() <= 120) return 1; //只需要区别开就好else return 2;//只是区别开就好}));System.out.println("collect = " + collect); } //输出:collect = {1=[Dish(name=apple, vegetarian=true, calories=50, type=OTHER), Dish(name=orange, vegetarian=true, calories=70, type=OTHER), Dish(name=banana, vegetarian=true, calories=60, type=OTHER)], // 2=[Dish(name=chicken, vegetarian=false, calories=350, type=MEAT), Dish(name=rich, vegetarian=true, calories=150, type=OTHER), Dish(name=pizza, vegetarian=true, calories=350, type=OTHER), Dish(name=fish, vegetarian=false, calories=250, type=FISH)]}
- 当然返回1和2是不清楚的,如果大于120算是高卡路里,否则就是低卡路里,那么就可以定义一个枚举,然后返回枚举值加以切分就好了
多级分组
- 如果我们不止只想分一层,比如我们要按是否是素食和肉食分组,然后再按卡路里是否<=60分组,这次我们不返回1和2,采用枚举返回,那么该怎么做
enum MyEnum{YES,NO} @Test public void test() throws Exception {Map<Boolean, Map<MyEnum, List<Dish>>> collect = menu.stream().collect(Collectors.groupingBy(Dish::isVegetarian, Collectors.groupingBy(dish -> {if (dish.getCalories() <= 60) return MyEnum.YES;else return MyEnum.NO;})));System.out.println(collect); } //{false={NO=[Dish(name=chicken, vegetarian=false, calories=350, type=MEAT), Dish(name=fish, vegetarian=false, calories=250, type=FISH)]}, // true={YES=[Dish(name=apple, vegetarian=true, calories=50, type=OTHER), Dish(name=banana, vegetarian=true, calories=60, type=OTHER)], // NO=[Dish(name=rich, vegetarian=true, calories=150, type=OTHER), Dish(name=pizza, vegetarian=true, calories=350, type=OTHER), Dish(name=orange, vegetarian=true, calories=70, type=OTHER)]}}
按group收集数据
- 上面多级分组我们看到可以把第二个groupingby收集器传递给外层收集器来实现多级分组。传递给第一个groupingby的第二个收集器可以是任何类型,而不一定还是一个groupingby
- 求每种(type)菜的个数
Map<Dish.Type, Long> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.counting())); //collect = {MEAT=1, FISH=1, OTHER=5} System.out.println("collect = " + collect);
- 求每组最高卡路里的dish
@Test public void test() throws Exception {Map<Dish.Type, Optional<Dish>> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))));System.out.println("collect = " + collect);//collect = {MEAT=Optional[Dish(name=chicken, vegetarian=false, calories=350, type=MEAT)],// OTHER=Optional[Dish(name=pizza, vegetarian=true, calories=350, type=OTHER)],// FISH=Optional[Dish(name=fish, vegetarian=false, calories=250, type=FISH)]} }
- 对于上面的代码,Map的第二个泛型是Option类型的,但是我们可以想到,如果menu中没有对应的type,那么根本就不可能到maxBy方法让其返回Option,所以在这的Option并不是很有用,反而影响了我们的查看和使用。那么我们就可以把它去掉
Map<Dish.Type, Dish> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType,Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get))); System.out.println("collect = " + collect); //collect = {OTHER=Dish(name=pizza, vegetarian=true, calories=350, type=OTHER), // FISH=Dish(name=fish, vegetarian=false, calories=250, type=FISH), // MEAT=Dish(name=chicken, vegetarian=false, calories=350, type=MEAT)}
- 对照上面,我们发现已经去掉了Option的包装,我们是用的Collectors.collectingAndThen方法,此方法接收两个参数:要转换的收集器和转换函数,首先他会找出最大的卡路里然后再将这个最大的卡路里对象进行转换:Option::get。所以我们的输出结果中就去掉了Optional
- 一些其他与groupingby使用的例子
- 求每组的卡路里总和
Map<Dish.Type, Integer> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.summingInt(Dish::getCalories))); //{OTHER=680, MEAT=350, FISH=250}
- 和mapping组合使用
public void test() throws Exception {Map<Dish.Type, Set<Integer>> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.mapping(dish -> {if (dish.getCalories() <= 120) return 1;else return 2;}, Collectors.toSet())));System.out.println("collect = " + collect); } //collect = {MEAT=[2], FISH=[2], OTHER=[1, 2]}
分区
- 分区是分组的一种特殊情况:使用一个Predicate函数作为分类函数,所以分区就只能分为true和false区
//分开素食和肉食 Map<Boolean, List<Dish>> collect = menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian)); //然后我们通过collect.get(true)就能找到素食了 //当然用filter过滤也可以只不过是只能过滤是素食或者是肉食的Dish了
分区的优势
- 分区的好处就比如上面代码演示了,filter只能保留一个结果的Dish,要不是素食的要么不是素食的,而分区就都能保留下来
- 分区的重载方法可以传入一个groupingby进行区内分组
Map<Boolean, Map<Dish.Type, List<Dish>>> collect = menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian, Collectors.groupingBy(Dish::getType))); //根据是否是素食分区后,再将每个区内细分为各个类型
- 分区后的第二个参数也可以再次传入一个分区进行二次分区
- 记住分区内只能传入返回boolean值的表达式,否则无法通过编译
收集器接口Collector
- 此接口为实现具体的归约操作提供了范本,之前的toList或者groupingby都是此接口实现的,自己也可以通过这个接口自定义归约实现
- 接口定义
public interface Collector<T, A, R> {/*** 建立新容器* 返回一个Supplier,他是用来创建一个空的累加器的实例,共数据收集过程使用*/Supplier<A> supplier();/*** 将元素添加到结果容器* 会返回执行归约操作的函数,他就是将元素处理后添加到累加器的,他会有两个参数,一个是累加器,一个是元素本身*/BiConsumer<A, T> accumulator();/*** 对结果容器应用最终转换* 必须返回在累加过程的最后要调用的一个函数,以便将累加器对象转换为整个集合操作的最终结果* 如果accumulator方法操作完之后已经符合期待类型,那么此方法可以原样返回不做操作*/Function<A, R> finisher();/*** 合并两个结果容器:用于并行* 会返回一个供归约操作使用的函数,定义了对流的各个子部分进行并行处理时,各个子部分归约所得的累加器要如何合并*/BinaryOperator<A> combiner();/*** 类似Spliterator中的characteristics方法* 会返回一个不可变的Characteristics集合,它定义了收集器的行为* 尤其是关于流是否可以并行归约,以及可以使用那些优化的提示* 包括三个枚举* CONCURRENT:accumulator方法可以从多个线程同事调用,且该收集器可以并行归约流,如果收集器没有标为UNORDERED,那么它仅在用于无序数据源时才可以并行归约* UNORDERED:归约结果不受流中项目的遍历和累积顺序的影响* IDENTITY_FINISH:累计器对象将会直接用作归约过程的最终结果,也就意味着,将累加器A不加检查的转换为结果R是安全的*/Set<Characteristics> characteristics();....}
- T是流中要收集的项目的泛型
- A是累加器的类型,累加器是在手机过程中用于累积部分结果的对象
- R是收集操作得到的对象的类型(收集器返回值类型)
实现一个类似toList()方法的功能
public class MyToList<T> implements Collector<T, List<T>, List<T>> {@Overridepublic Supplier<List<T>> supplier() {//创建一个list作为累加器供以后使用return ArrayList::new;}@Overridepublic BiConsumer<List<T>, T> accumulator() {//每次传入累加器和元素,然后把元素add到累加器//也可以写做 return (list,t) -> list.add(t);return List::add;}@Overridepublic BinaryOperator<List<T>> combiner() {//如果是并行的,那么这就是将累加器合并的操作return (l1,l2) -> {l1.addAll(l2);return l1;};}@Overridepublic Function<List<T>, List<T>> finisher() {//因为我们要实现的功能就是将值放入List,现在的累加器正好是我们所需要的List类型,所以直接返回就好啦//对于下面这个identity方法的解释//Returns a function that always returns its input argument//输入进来的在返回出去return Function.identity();}//@Overridepublic Set<Characteristics> characteristics() {//IDENTITY_FINISH:累计器对象将会直接用作归约过程的最终结果,因为我们不需要转换为其他类型的结果//CONCURRENT:可以从多个线程同事调用,且该收集器可以并行归约流,但是没有标为UNORDERED,那么它仅在用于无序数据源时才可以并行归约return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH,Characteristics.CONCURRENT));} }
进行自定义收集而不去实现Collector
- 对于IDENTITY_FINISH的收集操作,还有一种办法可以得到同样的结果而无需从头实现新的Collectors接口
- Stream有一个重载的collect方法可以接受另外三个方法-->supplier,accumulator,combiner,也就是说不用实现自己的类而是直接把实现逻辑传入固定的参数位置就能够使用,比如把Name收集到List
ArrayList<Object> collect = menu.stream().map(Dish::getName).collect(ArrayList::new,//创建累加器List::add,//对每个元素实现的累加器操作List::addAll);//并行组合累加器的操作
java8学习:用流收集数据相关推荐
- 《Java8实战》-第六章读书笔记(用流收集数据-01)
用流收集数据 我们在前一章中学到,流可以用类似于数据库的操作帮助你处理集合.你可以把Java 8的流看作花哨又懒惰的数据集迭代器.它们支持两种类型的操作:中间操作(如 filter 或 map )和终 ...
- java8流实战-用流收集数据实践简记
文章目录 收集器简介 什么是流收集器 一个get start告诉你流收集器的强大之处 流收集器工作原理简析 规约和汇总 查找流中的最大值和最小值 汇总和和一次性获取所有规约结果 连接字符串 关于规约汇 ...
- 用流收集数据Collectors的用法介绍分组groupingBy、分区partitioningBy(一)
文章目录 一.收集器简介 二.归约和汇总 1.查找流中最大值和最小值Collectors.maxBy和,Collectors.minBy 2.汇总 3.连接字符串 4.广义归约汇总 三.分组 1.多级 ...
- Java 8 (5) Stream 流 - 收集数据
在前面已经使用过collect终端操作了,主要是用来把Stream中的所有元素结合成一个List,在本章中,你会发现collect是一个归约操作,就像reduce一样可以接受各种做法作为参数,将流中的 ...
- java8实战:使用流收集数据之toList、joining、groupBy(多字段分组)(1)
return new CollectorImpl<CharSequence, StringBuilder, String>( StringBuilder::new, StringBuild ...
- 《Java8实战》笔记(06):用流收集数据
文章目录 收集器简介 收集器用作高级归约 预定义收集器 归约和汇总 查找流中的最大值和最小值 汇总 连接字符串 广义的归约汇总 Stream接口的collect和reduce有何不同 收集框架的灵活性 ...
- JAVA8学习9-自定义收集器(Characteristics 使用说明)
9 自定义收集器 在自定义收集器前,我们再确定下 Collector 接口函数接收参数和该实现的方法. public interface Collector<T, A, R> {// ...
- 【深度学习】【物联网】深度解读:深度学习在IoT大数据和流分析中的应用
作者|Natalie 编辑|Emily AI 前线导读:在物联网时代,大量的感知器每天都在收集并产生着涉及各个领域的数据.由于商业和生活质量提升方面的诉求,应用物联网(IoT)技术对大数据流进行分析是 ...
- 【深度学习】深度解读:深度学习在IoT大数据和流分析中的应用
来源:网络大数据(ID:raincent_com) 摘要:这篇论文对于使用深度学习来改进IoT领域的数据分析和学习方法进行了详细的综述. 在物联网时代,大量的感知器每天都在收集并产生着涉及各个领域的数 ...
最新文章
- nginx 配置简介
- 【转】XP/2000无法使用“缩略图查看”、右键无“设置桌面背景”选项问题详解...
- matlab变量由非标量,matlab中的if语句
- Redis所需内存 超过可用内存怎么办
- Phinx - 数据库迁移及版本控制介绍(内含中文文档翻译)
- apache httpd配置后启动失败或域名绑定无效等问题解决方法
- Web应用程序中Resource Bundle技术概述
- hosts文件可以干嘛?
- 冬天 计算机无法启动不了怎么办,每次到冬天电脑就开不了
- CAN总线学习心得:zlg关于can帖子汇总【转】【强烈推荐】
- 深入 AXI4总线 (四):RAM 读取实战
- 图片缩略图,CMYK图片变红问题解决
- java获取本机IP
- 指向字符串的指针 ------ 字符串指针
- 10、Horizon RDS远程桌面服务和应用程序池配置
- AutoCAD 2020快捷指令大全
- 计算机信息技术奥赛实践,2018信息学奥赛 成绩_浅谈中学信息学奥林匹克竞赛课程的建设...
- Java进阶:基于TCP的网络实时聊天室(socket通信案例)
- 插值、平稳假设、本征假设、变异函数、基台、块金、克里格、线性无偏最优…地学计算概念及公式推导
- python进制转化器(任意进制)