1. 流式编程简介
1.1 Stream编程的简介
Stream是JDK1.8之后出现的新特性,也是JDK1.8新特性中最值得学习的两种新特性之一。(另外一个是 lambda表达式)。
Stream是对集合操作的增强,流不是集合的元素,不是一种数据结构,不负责数据的存储的。流更像是 一个迭代器,可以单向的遍历一个集合中的每一个元素,并且不可循环。
1.2 为什么要使用集合的流式编程
有些时候,对集合中的元素进行操作的时候,需要使用到其他操作的结果。在这个过程中,集合的流式 编程可以大幅度的简化代码的数量。将数据源中的数据,读取到一个流中,可以对这个流中的数据进行 操作(删除、过滤、映射...)。每次的操作结果也是一个流对象,可以对这个流再进行其他的操作。
1.3 使用流式编程的步骤
通常情况下,对集合中的数据使用流式编程,需要经过以下三步。
-
获取数据源,将数据源中的数据读取到流中。
-
对流中的数据进行各种各样的处理。
-
对流中的数据进行整合处理。
在上述三个过程中,
过程2中,有若干方法,可以对流中的数据进行各种各样的操作,并且返回流对象 本身,这样的操作,被称为 -- 中间操作。
过程3中,有若干方法,可以对流中的数据进行各种处理,并关闭流,这样的操作,被称为 -- 最终操作。
在中间操作和最终操作中,基本上所有的方法参数都是函数式接口,可以使用lambda表达式来实现。 使用集合的流式编程,来简化代码量,是需要对 lambda 表达式做到熟练掌握。
2. 数据源的获取
2.1 数据源的介绍
数据源,顾名思义,既是流中的数据的来源。是集合的流式编程的第一步,将数据源中的数据读取到流 中,进行处理。注意:将数据读取到流中进行处理的时候,与数据源中的数据没有关系。也就是说,中间操作对流中的数据进行处理、过滤、映射、排序... ,此时是不会影响数据源中的数据的
2.2 数据源的获取
这个过程,其实是将一个容器中的数据,读取到一个流中。因此无论什么容器作为数据源,读取到流中 的方法返回值一定是一个Stream。
// 1、通过Collection接口中的stream()方法获取数据源为Collection的流 Stream<Integer> stream = list.stream(); // 2、通过Collection接口的parallelStream()方法获取数据源为Collection的流 Stream<Integer> stream = list.parallelStream(); // 3、通过Arrays工具类中的stream()方法获取数据源为数组的流 IntStream stream = Arrays.stream(array);
关于 stream() 和 parallelStream
他们都是Collection集合获取数据源的方法,不同点在于stream()方法获取的数据源是串行的,
parallelStream()获取的数据源是并行的。 parallelStream()内部集成了多个线程对流中的数据进行 操作,效率更高。
3. 最终操作
3.1 最终操作的简介
将流中的数据整合到一起,可以存入一个集合,也可以直接对流中的数据进行遍历、数据统计... ,通过 最终操作,需要掌握如何从流中提取出来我们想要的信息。
注意事项:最终操作,之所以叫最终操作,是因为,在最终操作执行结束后,会关闭这个流,流中的所有数据都会销毁。如果使用一个已经关闭了的流,会出现异常。
3.2 collect
将流中的数据收集到一起,对这些数据进行一些处理。最常见的处理,就是将流中的数据存入一个集 合。 collect方法的参数,是一个Collector接口,而且这个接口并不是一个函数式接口。实现这个接口, 可以自定义收集的规则。但是,绝大部分情况下,不需要自定义,直接使用Collectors工具类提供的方法即可
// 1.1、转成 List List<Integer> result1 = list.stream().collect(Collectors.toList()); System.out.println(result1); // 1.2、转成 Set Set<Integer> result2 = list.stream().collect(Collectors.toSet()); System.out.println(result2); // 1.3、转成 Map,提供两个函数式接口的实现,分别实现键的生成规则和值的生成规则 Map<Integer, Integer> result3 = list.stream().collect(Collectors.toMap(ele -> ele / 10, ele -> ele)); System.out.println(result3);
3.3 reduce
将流中的数据按照一定的规则聚合起来。
// 将流的元素,逐一带入到这个方法中,进行运算 // 最终的运算结果,得到的其实是一个 Optional 类型,需要使用get() 获取到里面的数据 int result4 = list.stream().reduce((e1, e2) -> e1 + e2).get(); System.out.println(result4);
3.4 count
统计流中的元素数量。
long result5 = list.stream().count(); System.out.println(result5);
3.5 forEach
迭代、遍历流中的数据。
list.stream().forEach(System.out::println);
3.6 max & min
获取流中的最大的元素、最小的元素。
// 获取最大值 Integer result6 = list.stream().max(Integer::compareTo).get(); System.out.println("max is : " + result6); // 获取最小值 Integer result7 = list.stream().min(Integer::compareTo).get(); System.out.println("min is : " + result7);
3.7 Matching
allMatch: 只有当流中所有的元素,都匹配指定的规则,才会返回 true
anyMatch: 只要流中有任意的数据,满足指定的规则,都会返回 true
noneMatch: 只有当流中的所有的元素,都不满足指定的规则,才会返回true
// 判断流中是否所有的元素都大于 50 boolean result8 = list.stream().allMatch(ele -> ele > 50); System.out.println(result8); // 判断流中是否有大于 50 的数据 boolean result9 = list.stream().anyMatch(ele -> ele > 50); System.out.println(result9); // 判断流中是否没有奇数 boolean result10 = list.stream().noneMatch(ele -> ele % 2 != 0); System.out.println(result10);
3.8 find
findFirst: 从流中获取一个元素(一般情况下,是获取的开头的元素)
findAny: 从流中获取一个元素(一般情况下,是获取的开头的元素)
这两个方法,绝大部分情况下,是完全相同的,但是在多线程的环境下, findAny和find返回的结果可能不一样。
Integer result11 = list.parallelStream().findFirst().get(); System.out.println(result11); Integer result12 = list.parallelStream().findAny().get(); System.out.println(result12);
3.9 最终操作的注意事项
最终操作,会关闭流。如果一个流被关闭了,再去使用这个流,就出出现异常。
// 9、最终操作错误示范 Stream<Integer> stream = list.stream(); long count = stream.count(); stream.forEach(System.out::println);
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java :279) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at com.qf.cstream.FinalOperationDemo.main(FinalOperationDemo.java:78)
4 .中间操作
4.1filter:过滤出来满足条件的数据
List<Integer> nums = new ArrayList<>(); nums.add(1); nums.add(2); nums.add(3); nums.add(4); nums.add(5); nums.add(2); nums.add(4); //找出满足条件的奇数 List<Integer> c1 = nums.stream().filter(x -> x % 2 != 0).collect(Collectors.toList()); System.out.println(c1);
4.2distinct:去重
List<Integer> nums = new ArrayList<>(); nums.add(1); nums.add(2); nums.add(3); nums.add(4); nums.add(5); nums.add(2); nums.add(4); nums.stream().distinct().forEach(System.out::println);
4.3 sorted:排序
-
无参是升序
-
有参传比较器,自定义比较规则
List<Integer> nums= new ArrayList<Integer>(); nums.add(3); nums.add(2); nums.add(5); nums.add(4); nums.add(1); nums.stream().sorted((a,b)-> b-a).forEach(System.out::println);
4.4 limit & skip
-
limit(long size):截取流中的前size个
-
skip(long size):跳过前size个元素
List<Integer> nums= new ArrayList<Integer>(); nums.add(3); nums.add(2); nums.add(5); nums.add(4); nums.add(1); nums.stream().limit(2).sorted((a,b)-> a-b).forEach(System.out::println); //不要2,升序排序,再跳过前size个元素 nums.stream().filter(e->e!=2).sorted().skip(2).forEach(System.out::println);
4.5 map & flatMap& mapToInt
List<List<Integer>> out = new ArrayList<>(); out.add(Arrays.asList(1,2,3)); out.add(Arrays.asList(4,5,6)); out.add(Arrays.asList(7,8,9)); /** * flatMap(): 传入lambda表达式 e->e.stream */ long count = out.stream().flatMap(e -> e.stream()).count(); System.out.println(count); //平均值 double asDouble = out.stream().flatMap(e -> e.stream()).mapToInt(e -> e).average().getAsDouble(); //最大值 OptionalInt max = out.stream().flatMap(e -> e.stream()).mapToInt(e -> e).max(); System.out.println(max.getAsInt()); System.out.println(asDouble);
4.5 Collectors工具类
Collectors是一个工具类,里面封装了很多方法,可以很方便的获取到一个 Collector 接口的实现类对 象,从而可以使用 collect() 方法,对流中的数据,进行各种各样的处理、整合。
Collectors.toList() : 将流中的数据,聚合到一个 List 集合中 Collectors.toSet() : 将流中的数据,聚合到一个 Set 集合中 Collectors.toMap() : 将流中的数据,聚合到一个 Map 集合中 maxBy() : 按照指定的规则,找到流中最大的元素,等同于 max minBy() : 按照指定的规则,找到流中最小的元素,等同于 min joining() : 将流中的数据拼接成一个字符串,注意:只能操作流中是String的数据 summingInt() : 将流中的数据,映射成 int 类型的数据,并求和 averagingInt() : 将流中的数据,映射成 int 类型的数据,并求平均值 summarizingInt() : 将流中的数据,映射成 int 类型的数据,并获取描述信息
// maxBy: 按照指定的规则,找到流中最大的元素,等同于 max Student max = list.stream() .collect(Collectors.maxBy((s1, s2) -> s1.getScore() -s2.getScore())) .get(); System.out.println(max); // minBy: 按照指定的规则,找到流中最小的元素,等同于 min Student min = list.stream() .collect(Collectors.minBy((s1, s2) -> s1.getScore() -s2.getScore())) .get(); System.out.println(min); // 将流中的数据,拼接起来 String s1 = list.stream() .map(Student::getName) .collect(Collectors.joining()); System.out.println(s1); // 将流中的数据,拼接起来,以指定的分隔符进行分隔 String s2 = list.stream() .map(Student::getName) .collect(Collectors.joining(", ")); System.out.println(s2); // 将流中的数据,拼接起来,以指定的分隔符进行分隔,并添加前缀和尾缀 String s3 = list.stream() .map(Student::getName) .collect(Collectors.joining(", ", "{", "}")); System.out.println(s3); // 将流中的数据,映射成 int 类型的数据,并求和 int sum = list.stream() .collect(Collectors.summingInt(Student::getScore)); System.out.println(sum); // 将流中的数据,映射成 int 类型的数据,并求平均值 double average = list.stream() .collect(Collectors.averagingInt(Student::getScore)); System.out.println(average); // 将流中的数据,映射成 int 类型的数据,并获取描述信息 IntSummaryStatistics summaryStatistics = list.stream() .collect(Collectors.summarizingInt(Student::getScore)); System.out.println(summaryStatistics); System.out.println(summaryStatistics.getCount()); System.out.println(summaryStatistics.getSum()); System.out.println(summaryStatistics.getMax()); System.out.println(summaryStatistics.getMin()); System.out.println(summaryStatistics.getAverage());
5. 综合案例
需求 : 一个集合中存储了了若干个Student对象 , 要求查询出以下结果 : 1. 所有及格的学生信息 2. 所有及格的学生姓名 3. 所有学生的平均成绩 4. 班级的前3名(按照成绩) 5. 班级的3-10名(按照成绩) 6. 所有不不及格的学生平均成绩 7. 将及格的学生 , 按照成绩降序输出所有信息 8. 班级学生的总分
public class Student { private String name; private int score; public String getName() { return name; } public int getScore() { return score; } public Student(String name, int score) { this.name = name; this.score = score; } @Override public String toString() { return String.format("姓名 : %s, 成绩 : %d", name, score); } }
import java.util.List; import java.util.ArrayList; import java.util.Collections; import java.util.stream.Collectors; public class Program { public static void main(String[] args) { // 0、实例化集合,存储学生对象 List<Student> list = new ArrayList<>(); Collections.addAll(list, new Student("xiaoming", 89), new Student("xiaobai", 98), new Student("xiaohei", 78), new Student("xiaolv", 86), new Student("xiaowang", 59), new Student("xiaoxiao", 100) ); // 1、所有及格的学生信息 System.out.println("-----------1、所有及格的学生信息-----------"); list.stream() .filter(s -> s.getScore() >=60) .forEach(System.out::println); // 2、所有及格的学生姓名 System.out.println("-----------2、所有及格的学生姓名 -----------"); list.stream() .filter(s -> s.getScore() >= 60) .map(Student::getName) .forEach(System.out::println); // 3、所有学生的平均成绩 System.out.println("-----------3、所有学生的平均成绩-----------"); double average = list.stream() .mapToInt(Student::getScore) .average() .getAsDouble(); System.out.println(average); // 4、班级的前3名(按照成绩) System.out.println("-----------4、班级的前3名(按照成绩)-----------"); List<Student> result1 = list.stream() .sorted((s1, s2) -> s2.getScore() - s1.getScore()) .limit(3) .collect(Collectors.toList()); result1.forEach(System.out::println); // 5、班级的3-10名(按照成绩) System.out.println("-----------5、班级的3-10名(按照成绩)-----------"); List<Student> result2 = list.stream() .sorted((s1, s2) -> s2.getScore() - s1.getScore()) .limit(10) .skip(2) .collect(Collectors.toList()); result2.forEach(System.out::println); // 6、所有不不及格的学生平均成绩 System.out.println("-----------6、所有不不及格的学生平均成绩-----------"); double average1 = list.stream() .filter(s -> s.getScore() < 60) .mapToInt(Student::getScore) .average() .getAsDouble(); System.out.println(average1); // 7、将及格的学生 , 按照成绩降序输出所有信息 System.out.println("-----------7、将及格的学生 , 按照成绩降序输出所有信息-----------"); list.stream() .filter(s -> s.getScore() >= 60) .sorted((s1, s2) -> s2.getScore() - s1.getScore()) .forEach(System.out::println); // 8、班级学生的总分 System.out.println("-----------8、班级学生的总分 -----------"); long sum = list.stream() .mapToInt(Student::getScore) .sum(); System.out.println(sum); } }