lambda表达式与流处理(三)

发布时间 2023-11-07 16:32:09作者: 小粥123456789

14.3 流处理

流处理有点类似数据库的SQL语句,可以执行非常复杂的过滤、映射、查找和收集功能,并且代码量很少。

1.流处理的接口都定义在java.uil.stream包下。BaseStream接口是最基础的接口,但最常用的是BaseStream接口的一个子接口——Stream接口,基本上绝大多数的流处理都是在Stream接口上实现的。

2.Stream接口是泛型接口,所以流中操作的元素可以是任何类的对象。

Stream接口的常用方法如表14.3所示。

表14.3中最后一列“类型”中有两个值:中间操作和终端操作。中间操作类型的方法会生成一个新的流对象,被操作的流对象仍然可以执行其他操作终端操作会消费流,操作结束之后,被操作的流对象就不能再次执行其他操作了。这是两者的最大区别。

 Collection接口新增两个可以获取流对象的方法。第一个方法最常用,可以获取集合的顺序流,方法如下:

因为所有集合类都是Collection接口的子类,如ArrayList类、HashSet类等,所以这些类都可以进行流处理。例如:

 14.3.2 Optional类

1.Optional类像是一个容器,可以保存任何对象,并且针对NullPointerException空指针异常做了优化,保证Optional类保存的值不会是null。因此,Optional类是针对“对象可能是null也可能不是null”的场景为开发者提供了优质的解决方案,减少了烦琐的异常处理。

2.Optional类是用final修饰的,所以不能有子类。Optional类是带有泛型的类,所以该类可以保存任何对象的值。

optional类中有一个叫作value的成员属性,这个属性就是用来保存具体值的。value是用泛型T修饰的,并且还用了final修饰,这表示一个Optional对象只能保存一个值。

 

 14.3.3 Collectors类

Collectors类为收集器类,该类实现了java.util.Collector接口,可以将Stream流对象进行各种各样的封装、归集、分组等操作。

同时,Collectors类还提供了很多实用的数据加工方法,如数据统计计算等。Collectors类的常用方法如表14.5所示。

14.3.4 数据过滤

 

 数据过滤就是在杂乱的数据中筛选出需要的数据,类似SQL语句中的WHERE关键字,给出一定的条件,将符合条件的数据过滤并展示出来。

1.filter()方法filter()方法是Stream接口提供的过滤方法。该方法可以将lambda表达式作为参数,然后按照lambda表达式的逻辑过滤流中的元素。过滤出想要的流元素后,还需使用Stream提供的collect()方法按照指定方法重新封装。

【例14.16】输出1~10中的所有奇数

这个实例把“获取流”“过滤流”“封装流”3个部分操作分开编写,是为了方便读者学习理解,通常为了代码简洁,3部分操作可以写在一行代码中,例如:

这种写法也可以避免终端操作造成的“流被消费掉”的问题,因为每次被操作的流都是从集合中重新获取的。

 

【例14.17】找出年龄大于30的员工

本实例使用了例14.14定义的Employee类,在获取员工集合后,将年龄大于30的员工过滤出来。如果将员工集合返回的流对象泛型定义为<Employee>,就可以直接在lambda表达式中使用Employee类的方法

2.distinct()方法

distinct()方法是Stream接口提供的过滤方法。该方法可以去除流中的重复元素,效果与SQL语句中的DISTINCT关键字一样。

【例14.18】去除List集合中的重复数字

因为distinct()方法属于中间操作,所以可以配合filter()方法一起使用。

3.limit()方法

limit()方法是Stream接口提供的方法,该方法可以获取流中前N个元素。

 【例14.19】找出所有员工列表中的前两位女员工

 4.skip()方法

skip()方法是Stream接口提供的方法,该方法可以忽略流中的前N个元素。

【例14.20】取出所有男员工,并忽略前两位男员工

14.3.5 数据映射

数据的映射和过滤概念不同:过滤是在流中找到符合条件的元素,映射是在流中获得具体的数据。

Stream接口提供了map()方法用来实现数据映射,map()方法会按照参数中的函数逻辑获取新的流对象,新的流对象中元素类型可能与旧流对象元素类型不相同。

【例14.21】获取开发部所有员工的名单

本实例使用了例14.14定义的Employee类,在获取员工集合后,先过滤出开发部的员工,再引用员工类的getName()方法作为map()方法的映射参数,这样就获取到开发部员工名单。

 结果输出了开发部两位员工的名字,但没有输出这两位员工的其他信息,这个就是映射的结果。

除了可以映射出员工名单,还可以对映射数据进行加工处理。例如,统计销售部一个月的薪资总额。因为涉及数字计算,所以需要让Stream对象转为可以进行数学运算的数字流。因为薪资类型是double类型,所以应该调用mapToDouble()方法进行转换。

【例14.22】计算销售部一个月的薪资总额

除DoubleStream类外,java.util.stream包还提供了IntStream类和LongStream类以应对不同的计算场景。

14.3.6 数据查找

本节所讲的数据查找并不是在流中获取数据(这属于数据过滤),而是判断流中是否有符合条件的数据,查找的结果是一个boolean值或一个Optional类的对象。本节将讲解allMatch()、anyMatch()、noneMatch()和findFirst()这4个方法。

1.allMatch()方法allMatch()方法是Stream接口提供的方法,该方法会判断流中的元素是否全部符合某一条件,返回结果是boolean值。如果所有元素都符合条件则返回true,否则返回false。

【例14.23】检查所有员工是否都大于25岁

2.anyMatch()方法anyMatch()方法是Stream接口提供的方法,该方法会判断流中的元素是否有符合某一条件,只要有一个元素符合条件就返回true,如果没有元素符合条件才会返回false。

3.noneMatch()方法

noneMatch()方法是Stream接口提供的方法,该方法会判断流中的所有元素是否都不符合某一条件。这个方法的逻辑和allMatch()方法正好相反。

【例14.25】检查公司是否不存在薪资小于2000元的员工

4.findFirst()方法

findFirst()方法是Stream接口提供的方法,这个方法会返回符合条件的第一个元素。

注意这个方法的返回值不是boolean值,而是一个Optional对象。

 14.3.7 数据收集

 

 数据收集可以理解为高级的“数据过滤+数据映射”,是对数据的深加工。本节将讲解两种实用场景:数据统计和数据分组。

1.数据统计

数据统计不仅可以筛选出特殊元素,还可以对元素的属性进行统计计算。这种复杂的统计操作不是由Stream实现的,而是由Collectors收集器类实现的,收集器提供了非常丰富的API,有着强大的数据挖掘能力。

【例14.27】统计公司各项数据,打印成报表

 2.数据分组

数据分组就是将流中元素按照指定的条件分开保存,类似SQL语言中的“GROUP BY”关键字。分组之后的数据会按照不同的标签分别保存成一个集合,然后按照“键-值”关系封装在Map对象中。

数据分组有一级分组和多级分组两种场景

Collectors类提供的groupingBy()方法就是用来进行分组的方法,方法参数是一个Function接口对象,收集器会按照指定的函数规则对数据进行分组。

【例14.28】将所有员工按照部门分组

 介绍完一级分组后,再介绍一下复杂的多级分组。

一级分组是按照一个条件进行分组,那么多级分组就是按照多个条件进行分组。还是用学校举例,学校有100个学生,这些学生分布在3个年级中,这是一级分组,但每个年级还有若干个班级,学生们分到不同年级之后又分到不同的班,这就是二级分组。如果学生再按男女分组,就变成了三级分组。元素按照两个以上的条件进行分组,就是多级分组。

Collectors类提供的groupingBy()方法还提供了一个重载形式:

这个重载方法的第二个参数也是一个收集器,当分组前数据包含其他分组的结果,这就构成了多级分组功能。

【例14.29】将所有员工先按照部门分组,再按照性别分组