流操做是Java8提供一个重要新特性,它容许开发人员以声明性方式处理集合,其核心类库主要改进了对集合类的 API和新增Stream操做。Stream类中每个方法都对应集合上的一种操做。将真正的函数式编程引入到Java中,能 让代码更加简洁,极大地简化了集合的处理操做,提升了开发的效率和生产力。java
同时stream不是一种数据结构,它只是某种数据源的一个视图,数据源能够是一个数组,Java容器或I/O channel等。在Stream中的操做每一次都会产生新的流,内部不会像普通集合操做同样马上获取值,而是惰性 取值,只有等到用户真正须要结果的时候才会执行。而且对于如今调用的方法,自己都是一种高层次构件,与线程模型无关。所以在并行使用中,开发者们无需再去操 心线程和锁了。Stream内部都已经作好了。sql
若是刚接触流操做的话,可能会感受不太舒服。其实理解流操做的话能够对比数据库操做。把流的操做理解为对数据库中 数据的查询操做 集合 = 数据表 元素 = 表中的每条数据 属性 = 每条数据的列 流API = sql查询
Stream流接口中定义了许多对于集合的操做方法,总的来讲能够分为两大类:中间操做和终端操做。数据库
中间操做:会返回一个流,经过这种方式能够将多个中间操做链接起来,造成一个调用链,从而转换为另外 一个流。除非调用链后存在一个终端操做,不然中间操做对流不会进行任何结果处理。编程
终端操做:会返回一个具体的结果,如boolean、list、integer等。数组
一、筛选数据结构
对于集合的操做,常常性的会涉及到对于集中符合条件的数据筛选,Stream中对于数据筛选两个常见的API: filter(过滤)、distinct(去重)app
1.1基于filter()实现数据过ide
该方法会接收一个返回boolean的函数做为参数,终返回一个包括全部符合条件元素的流。函数式编程
案例:获取全部年龄20岁如下的学生函数
/** * @author 我是七月呀 * @date 2020/12/22 */ public class FilterDemo { public static void main(String[] args) { //获取全部年龄20岁如下的学生 ArrayList<Student> students = new ArrayList<>(); students.add(new Student(1,19,"张三","M",true)); students.add(new Student(1,18,"李四","M",false)); students.add(new Student(1,21,"王五","F",true)); students.add(new Student(1,20,"赵六","F",false)); students.stream().filter(student -> student.getAge()<20); } }
源码解析
此处能够看到filter方法接收了Predicate函数式接口。
首先判断predicate是否为null,若是为null,则抛出NullPointerException;构建Stream,重写opWrapsink方法。参数flags:下一个sink的标志位,供优化使用。参数sink:下一个sink,经过此参数将sink构形成单链。此时流已经构建好,可是由于begin()先执行,此时是没法肯定流中后续会存在多少元素的,因此传递-1,表明没法肯定。最后调用Pridicate中的test,进行条件判断,将符合条件数据放入流中。
1.2基于distinct实现数据去重
/** * @author 我是七月呀 * @date 2020/12/22 */ public class DistinctDemo { public static void main(String[] args) { List<Integer> integers = Arrays.asList(1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 2, 2, 2, 2); integers.stream().distinct().collect(Collectors.toList()); } }
源码解析
根据其源码,咱们能够知道在distinct()内部是基于LinkedHashSet对流中数据进行去重,并终返回一个新的流。
二、切片
2.1基于limit()实现数据截取
该方法会返回一个不超过给定长度的流
案例:获取数组的前五位
/** * @author 我是七月呀 * @date 2020/12/22 */ public class LimitDemo { public static void main(String[] args) { //获取数组的前五位 List<Integer> integers = Arrays.asList(1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 2, 2, 2, 2); integers.stream().limit(5); } }
源码解析:
对于limit方法的实现,它会接收截取的长度,若是该值小于0,则抛出异常,不然会继续向下调用 SliceOps.makeRef()。该方法中this表明当前流,skip表明须要跳过元素,比方说原本应该有4个元素,当跳过元素 值为2,会跳过前面两个元素,获取后面两个。maxSize表明要截取的长度
在makeRef方法中的unorderedSkipLimitSpliterator()中接收了四个参数Spliterator,skip(跳过个数)、limit(截取 个数)、sizeIfKnown(已知流大小)。若是跳过个数小于已知流大小,则判断跳过个数是否大于0,若是大于则取截取 个数或已知流大小-跳过个数的二者小值,不然取已知流大小-跳过个数的结果,做为跳过个数。
后对集合基于跳过个数和截取个数进行切割。
2.2基于skip()实现数据跳过
案例:从集合第三个开始截取5个数据
/** * @author 我是七月呀 * @date 2020/12/22 */ public class LimitDemo { public static void main(String[] args) { //从集合第三个开始截取5个数据 List<Integer> integers = Arrays.asList(1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 2, 2, 2, 2); List<Integer> collect = integers.stream().skip(3).limit(5).collect(Collectors.toList()); collect.forEach(integer -> System.out.print(integer+" ")); } }
结果4 4 5 5 6
案例:先从集合中截取5个元素,而后取后3个
/** * @author 我是七月呀 * @date 2020/12/22 */ public class LimitDemo { public static void main(String[] args) { //先从集合中截取5个元素,而后取后3个 List<Integer> integers = Arrays.asList(1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 2, 2, 2, 2); List<Integer> collect = integers.stream().limit(5).skip(2).collect(Collectors.toList()); collect.forEach(integer -> System.out.print(integer+" ")); } }
结果:3 4 4
源码分析:
在skip方法中接收的n表明的是要跳过的元素个数,若是n小于0,抛出非法参数异常,若是n等于0,则返回当前 流。若是n小于0,才会调用makeRef()。同时指定limit参数为-1.
此时能够发现limit和skip都会进入到该方法中,在肯定limit值时,若是limit<0,则获取已知集合大小长度-跳过的长度。最终进行数据切割。
三、映射
在对集合进行操做的时候,咱们常常会从某些对象中选择性的提取某些元素的值,就像编写sql同样,指定获取表 中特定的数据列
#指定获取特定列 SELECT name FROM student
在Stream API中也提供了相似的方法,map()。它接收一个函数做为方法参数,这个函数会被应用到集合中每个 元素上,并终将其映射为一个新的元素。
案例:获取全部学生的姓名,并造成一个新的集合
/** * @author 我是七月呀 * @date 2020/12/22 */ public class MapDemo { public static void main(String[] args) { //获取全部学生的姓名,并造成一个新的集合 ArrayList<Student> students = new ArrayList<>(); students.add(new Student(1,19,"张三","M",true)); students.add(new Student(1,18,"李四","M",false)); students.add(new Student(1,21,"王五","F",true)); students.add(new Student(1,20,"赵六","F",false)); List<String> collect = students.stream().map(Student::getName).collect(Collectors.toList()); collect.forEach(s -> System.out.print(s + " ")); } }
结果:张三 李四 王五 赵六
源码解析:
内部对Function函数式接口中的apply方法进行实现,接收一个对象,返回另一个对象,并把这个内容存入当前 流中,后返回
四、匹配
在平常开发中,有时还须要判断集合中某些元素是否匹配对应的条件,若是有的话,在进行后续的操做。在 Stream API中也提供了相关方法供咱们进行使用,如anyMatch、allMatch等。他们对应的就是&&和||运算符。
4.1基于anyMatch()判断条件至少匹配一个元素
anyMatch()主要用于判断流中是否至少存在一个符合条件的元素,它会返回一个boolean值,而且对于它的操做, 通常叫作短路求值
案例:判断集合中是否有年龄小于20的学生
/** * @author 我是七月呀 * @date 2020/12/22 */ public class AnyMatchDemo { public static void main(String[] args) { //判断集合中是否有年龄小于20的学生 ArrayList<Student> students = new ArrayList<>(); students.add(new Student(1,19,"张三","M",true)); students.add(new Student(1,18,"李四","M",false)); students.add(new Student(1,21,"王五","F",true)); students.add(new Student(1,20,"赵六","F",false)); if(students.stream().anyMatch(student -> student.getAge() < 20)){ System.out.println("集合中有年龄小于20的学生"); }else { System.out.println("集合中没有年龄小于20的学生"); } } }
根据上述例子能够看到,当流中只要有一个符合条件的元素,则会马上停止后续的操做,当即返回一个布尔值,无需遍历整个流。
源码解析:
内部实现会调用makeRef(),其接收一个Predicate函数式接口,并接收一个枚举值,该值表明当前操做执行的是 ANY。
若是test()抽象方法执行返回值==MatchKind中any的stopOnPredicateMatches,则将stop中断置为true,value 也为true。并终进行返回。无需进行后续的流操做。
4.2基于allMatch()判断条件是否匹配全部元素
allMatch()的工做原理与anyMatch()相似,可是anyMatch执行时,只要流中有一个元素符合条件就会返回true, 而allMatch会判断流中是否全部条件都符合条件,所有符合才会返回true
案例:判断集合全部学生的年龄是否都小于20
/** * @author 我是七月呀 * @date 2020/12/22 */ public class AllMatchDemo { public static void main(String[] args) { //判断集合全部学生的年龄是否都小于20 ArrayList<Student> students = new ArrayList<>(); students.add(new Student(1,19,"张三","M",true)); students.add(new Student(1,18,"李四","M",false)); students.add(new Student(1,21,"王五","F",true)); students.add(new Student(1,20,"赵六","F",false)); if(students.stream().allMatch(student -> student.getAge() < 20)){ System.out.println("集合全部学生的年龄都小于20"); }else { System.out.println("集合中有年龄大于20的学生"); } } }
源码解析:与anyMatch相似,只是其枚举参数的值为ALL
五、查找
对于集合操做,有时须要从集合中查找中符合条件的元素,Stream中也提供了相关的API,findAny()和 findFirst(),他俩能够与其余流操做组合使用。findAny用于获取流中随机的某一个元素,findFirst用于获取流中的 第一个元素。至于一些特别的定制化需求,则须要自行实现。
5.1基于findAny()查找元素
案例:findAny用于获取流中随机的某一个元素,而且利用短路在找到结果时,当即结束
/** * @author 我是七月呀 * @date 2020/12/22 */ public class FindAnyDemo { public static void main(String[] args) { //findAny用于获取流中随机的某一个元素,而且利用短路在找到结果时,当即结束 ArrayList<Student> students = new ArrayList<>(); students.add(new Student(1,19,"张三1","M",true)); students.add(new Student(1,18,"张三2","M",false)); students.add(new Student(1,21,"张三3","F",true)); students.add(new Student(1,20,"张三4","F",false)); students.add(new Student(1,20,"张三5","F",false)); students.add(new Student(1,20,"张三6","F",false)); Optional<Student> student1 = students.stream().filter(student -> student.getSex().equals("F")).findAny(); System.out.println(student1.toString()); } }
结果:Optional[Student{id=1, age=21, name='张三3', sex='F', isPass=true}]
此时咱们将其循环100次
/** * @author 我是七月呀 * @date 2020/12/22 */ public class FindAnyDemo { public static void main(String[] args) { //findAny用于获取流中随机的某一个元素,而且利用短路在找到结果时,当即结束 ArrayList<Student> students = new ArrayList<>(); students.add(new Student(1,19,"张三1","M",true)); students.add(new Student(1,18,"张三2","M",false)); students.add(new Student(1,21,"张三3","F",true)); students.add(new Student(1,20,"张三4","F",false)); students.add(new Student(1,20,"张三5","F",false)); students.add(new Student(1,20,"张三6","F",false)); for (int i = 0; i < 100; i++) { Optional<Student> student1 = students.stream().filter(student -> student.getSex().equals("F")).findAny(); System.out.println(student1.toString()); } } }
结果:
因为数量较大,只截取了部分截图,所有都是同样的,不行的小伙伴能够本身测试一下
这时候咱们改成串行流在执行一下
/** * @author 我是七月呀 * @date 2020/12/22 */ public class FindAnyDemo { public static void main(String[] args) { //findAny用于获取流中随机的某一个元素,而且利用短路在找到结果时,当即结束 ArrayList<Student> students = new ArrayList<>(); students.add(new Student(1,19,"张三1","M",true)); students.add(new Student(1,18,"张三2","M",false)); students.add(new Student(1,21,"张三3","F",true)); students.add(new Student(1,20,"张三4","F",false)); students.add(new Student(1,20,"张三5","F",false)); students.add(new Student(1,20,"张三6","F",false)); for (int i = 0; i < 100; i++) { Optional<Student> student1 = students.parallelStream().filter(student -> student.getSex().equals("F")).findAny(); System.out.println(student1.toString()); } } }
结果:
如今咱们经过源码解析来分析下这是为何?
根据这一段源码介绍,findAny对于同一数据源的屡次操做会返回不一样的结果。可是,咱们如今的操做是串行的, 因此在数据较少的状况下,通常会返回第一个结果,可是若是在并行的状况下,那就不能确保返回的是第一个了。 这种设计主要是为了获取更加高效的性能。并行操做后续会作详细介绍。
传递参数,指定没必要须获取第一个元素
在该方法中,主要用于判断对于当前的操做执行并行仍是串行。
在该方法中的wrapAndCopyInto()内部作的会判断流中是否存在符合条件的元素,若是有的话,则会进行返回。结 果终会封装到Optional中的IsPresent中。
总结:当为串行流且数据较少时,获取的结果通常为流中第一个元素,可是当为并流行的时 候,则会随机获取。
5.2基于findFirst()查找元素
findFirst使用原理与findAny相似,只是它不管串行流仍是并行流都会返回第一个元素,这里不作详解
六、归约
到如今截止,对于流的终端操做,咱们返回的有boolean、Optional和List。可是在集合操做中,咱们常常会涉及 对元素进行统计计算之类的操做,如求和、求大值、小值等,从而返回不一样的数据结果。
6.1基于reduce()进行累积求和
案例:对集合中的元素求和
/** * @author 我是七月呀 * @date 2020/12/22 */ public class ReduceDemo { public static void main(String[] args) { List<Integer> integers = Arrays.asList(1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 2, 2, 2, 2); Integer reduce = integers.stream().reduce(0, (integer1, integer2) -> integer1 + integer2); System.out.println(reduce); } }
结果:53
在上述代码中,在reduce里的第一个参数声明为初始值,第二个参数接收一个lambda表达式,表明当前流中的两 个元素,它会反复相加每个元素,直到流被归约成一个终结果
Integer reduce = integers.stream().reduce(0,Integer::sum);
优化成这样也是能够的。固然,reduce还有一个不带初始值参数的重载方法,可是要对返回结果进行判断,由于若是流中没有任何元素的话,可能就没有结果了。具体方法以下所示
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 2, 2, 2, 2); Optional<Integer> reduce = integers.stream().reduce(Integer::sum); if(reduce.isPresent()){ System.out.println(reduce); }else { System.out.println("数据有误"); }
源码解析:两个参数的reduce方法
在上述方法中,对于流中元素的操做,当执行第一个元素,会进入begin方法,将初始化的值给到state,state就 是后的返回结果。并执行accept方法,对state和第一个元素根据传入的操做,对两个值进行计算。并把终计 算结果赋给state。
当执行到流中第二个元素,直接执行accept方法,对state和第二个元素对两个值进行计算,并把终计算结果赋 给state。后续依次类推。
能够按照下述代码进行理解
T result = identity; for (T element : this stream){ result = accumulator.apply(result, element) } return result;
源码解析:单个参数的reduce方法
在这部分实现中,对于匿名内部类中的empty至关因而一个开关,state至关于结果。
对于流中第一个元素,首先会执行begin()将empty置为true,state为null。接着进入到accept(),判断empty是否 为true,若是为true,则将empty置为false,同时state置为当前流中第一个元素,当执行到流中第二个元素时, 直接进入到accpet(),判断empty是否为true,此时empty为false,则会执行apply(),对当前state和第二个元素进 行计算,并将结果赋给state。后续依次类推。
当整个流操做完以后,执行get(), 若是empty为true,则返回一个空的Optional对象,若是为false,则将后计算 完的state存入Optional中。
能够按照下述代码进行理解:
boolean flag = false; T result = null; for (T element : this stream) { if (!flag) { flag = true; result = element; }else{ result = accumulator.apply(result, element); } } return flag ? Optional.of(result) : Optional.empty();
6.2获取流中元素的最大值、最小值
案例:获取集合中元素的最大值、最小值
/** * @author 我是七月呀 * @date 2020/12/22 */ public class MaxDemo { public static void main(String[] args) { List<Integer> integers = Arrays.asList(1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 2, 2, 2, 2); /** * 获取集合中的最大值 */ //方法一 Optional<Integer> max1 = integers.stream().reduce(Integer::max); if(max1.isPresent()){ System.out.println(max1); } //方法二 Optional<Integer> max2 = integers.stream().max(Integer::compareTo); if(max2.isPresent()){ System.out.println(max2); } /** * 获取集合中的最小值 */ //方法一 Optional<Integer> min1 = integers.stream().reduce(Integer::min); if(min1.isPresent()){ System.out.println(min1); } //方法二 Optional<Integer> min2 = integers.stream().min(Integer::compareTo); if(min2.isPresent()){ System.out.println(min2); } } }
结果:
Optional[8] Optional[8] Optional[1] Optional[1]
七、收集器
经过使用收集器,可让代码更加方便的进行简化与重用。其内部主要核心是经过Collectors完成更加复杂的计算 转换,从而获取到终结果。而且Collectors内部提供了很是多的经常使用静态方法,直接拿来就能够了。比方说: toList。
/** * @author 我是七月呀 * @date 2020/12/22 */ public class CollectDemo { public static void main(String[] args) { ArrayList<Student> students = new ArrayList<>(); students.add(new Student(1,19,"张三","M",true)); students.add(new Student(1,18,"李四","M",false)); students.add(new Student(1,21,"王五","F",true)); students.add(new Student(1,20,"赵六","F",false)); //经过counting()统计集合总数 方法一 Long collect = students.stream().collect(Collectors.counting()); System.out.println(collect); //结果 4 //经过count()统计集合总数 方法二 long count = students.stream().count(); System.out.println(count); //结果 4 //经过maxBy求最大值 Optional<Student> collect1 = students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge))); if(collect1.isPresent()){ System.out.println(collect1); } //结果 Optional[Student{id=1, age=21, name='王五', sex='F', isPass=true}] //经过max求最大值 Optional<Student> max = students.stream().max(Comparator.comparing(Student::getAge)); if(max.isPresent()){ System.out.println(max); } //结果 Optional[Student{id=1, age=21, name='王五', sex='F', isPass=true}] //经过minBy求最小值 Optional<Student> collect2 = students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge))); if(collect2.isPresent()){ System.out.println(collect2); } //结果 Optional[Student{id=1, age=18, name='李四', sex='M', isPass=false}] //经过min求最小值 Optional<Student> min = students.stream().min(Comparator.comparing(Student::getAge)); if(min.isPresent()){ System.out.println(min); } //结果 Optional[Student{id=1, age=18, name='李四', sex='M', isPass=false}] //经过summingInt()进行数据汇总 Integer collect3 = students.stream().collect(Collectors.summingInt(Student::getAge)); System.out.println(collect3); //结果 78 //经过averagingInt()进行平均值获取 Double collect4 = students.stream().collect(Collectors.averagingInt(Student::getAge)); System.out.println(collect4); //结果 19.5 //经过joining()进行数据拼接 String collect5 = students.stream().map(Student::getName).collect(Collectors.joining()); System.out.println(collect5); //结果 张三李四王五赵六 //复杂结果的返回 IntSummaryStatistics collect6 = students.stream().collect(Collectors.summarizingInt(Student::getAge)); double average = collect6.getAverage(); long sum = collect6.getSum(); long count1 = collect6.getCount(); int max1 = collect6.getMax(); int min1 = collect6.getMin(); } }
八、分组
在数据库操做中,常常会经过group by对查询结果进行分组。同时在平常开发中,也常常会涉及到这一类操做, 如经过性别对学生集合进行分组。若是经过普通编码的方式须要编写大量代码且可读性很差。
对于这个问题的解决,java8也提供了简化书写的方式。经过 Collectors。groupingBy()便可。
//经过性别对学生进行分组 Map<String, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getSex));
结果 { F=[Student{id=1, age=21, name='王五', sex='F', isPass=true}, Student{id=1, age=20, name='赵六', sex='F', isPass=false}], M=[Student{id=1, age=19, name='张三', sex='M', isPass=true}, Student{id=1, age=18, name='李四', sex='M', isPass=false}] }
8.1多级分组
刚才已经使用groupingBy()完成了分组操做,可是只是经过单一的sex进行分组,那如今若是需求发生改变,还要 按照是否及格进行分组,可否实现?答案是能够的。对于groupingBy()它提供了两个参数的重载方法,用于完成这 种需求。
这个重载方法在接收普通函数以外,还会再接收一个Collector类型的参数,其会在内层分组(第二个参数)结果,传 递给外层分组(第一个参数)做为其继续分组的依据。
//现根据是否经过考试对学生分组,在根据性别分组 Map<String, Map<Boolean, List<Student>>> collect1 = students.stream().collect(Collectors.groupingBy(Student::getSex, Collectors.groupingBy(Student::getPass)));
结果: { F={ false=[Student{id=1, age=20, name='赵六', sex='F', isPass=false}], true=[Student{id=1, age=21, name='王五', sex='F', isPass=true}] }, M={ false=[Student{id=1, age=18, name='李四', sex='M', isPass=false}], true=[Student{id=1, age=19, name='张三', sex='M', isPass=true}]} }
8.2多级分组变形
在平常开发中,咱们颇有可能不是须要返回一个数据集合,还有可能对数据进行汇总操做,比方说对于年龄18岁 的经过的有多少人,未及格的有多少人。所以,对于二级分组收集器传递给外层分组收集器的能够任意数据类型, 而不必定是它的数据集合。
//根据年龄进行分组,获取并汇总人数 Map<Integer, Long> collect2 = students.stream().collect(Collectors.groupingBy(Student::getAge, Collectors.counting())); System.out.println(collect2);
结果:{18=1, 19=1, 20=1, 21=1}
//要根据年龄与是否及格进行分组,并获取每组中年龄的学生 Map<Integer, Map<Boolean, Student>> collect3 = students.stream().collect(Collectors.groupingBy(Student::getAge, Collectors.groupingBy(Student::getPass, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparing(Student::getAge)), Optional::get)))); System.out.println(collect3.toString());
结果:{ 18={false=Student{id=1, age=18, name='李四', sex='M', isPass=false}}, 19={true=Student{id=1, age=19, name='张三', sex='M', isPass=true}}, 20={false=Student{id=1, age=20, name='赵六', sex='F', isPass=false}}, 21={true=Student{id=1, age=21, name='王五', sex='F', isPass=true}}}