在我接触到java8流式处理的时候,个人第一感受是流式处理让集合操做变得简洁了许多,一般咱们须要多行代码才能完成的操做,借助于流式处理能够在一行中实现。好比咱们但愿对一个包含整数的集合中筛选出全部的偶数,并将其封装成为一个新的List返回,那么在java8以前,咱们须要经过以下代码实现:java
List<Integer> evens = new ArrayList<>(); for (Integer num : nums){ if (num %2 == 0){ evens.add(num); } }
经过java8的流式处理,咱们能够将代码简化为:数组
List<Integer> evens = nums.stream().filter(num -> num %2 == 0).collect(Collectors.toList());
先简单解释一下上面这行语句的含义,stream()操做将集合转换成一个流,filter执行咱们自定义的筛选处理,这里是经过lambda表达式筛选出全部偶数,最后咱们经过collect()对结果进行封装处理,并经过Clectors.toList()指定其封装成为一个List集合返回。安全
由上面的例子能够看出,java8的流式处理极大的简化了对于集合的操做,实际上不光是集合,包括数组、文件等,只要是能够转换成流,咱们均可以借助流式处理,相似于咱们写SQL语句同样对其进行操做。java8经过内部迭代来实现对流的处理,一个流式处理能够分为三个部分:转换成流、中间操做、终端操做。以下图: app
以集合为例,一个流式处理的操做咱们首先须要调用stream()函数将其转换成流,而后再调用相应的中间操做达到咱们须要对集合进行的操做,好比筛选、转换等,最后经过终端操做对前面的结果进行封装,返回咱们须要的形式。jvm
咱们定义一个简单的学生实体类,用于后面的例子演示:函数
public class Student { /** 学号 */ private long id; private String name; private int age; /** 年级 */ private int grade; /** 专业 */ private String major; /** 学校 */ private String school; // 省略getter和setter }
// 初始化 List<Student> students = new ArrayList<Student>() { { add(new Student(20160001, "孔明", 20, 1, "土木工程", "武汉大学")); add(new Student(20160002, "伯约", 21, 2, "信息安全", "武汉大学")); add(new Student(20160003, "玄德", 22, 3, "经济管理", "武汉大学")); add(new Student(20160004, "云长", 21, 2, "信息安全", "武汉大学")); add(new Student(20161001, "翼德", 21, 2, "机械与自动化", "华中科技大学")); add(new Student(20161002, "元直", 23, 4, "土木工程", "华中科技大学")); add(new Student(20161003, "奉孝", 23, 4, "计算机科学", "华中科技大学")); add(new Student(20162001, "仲谋", 22, 3, "土木工程", "浙江大学")); add(new Student(20162002, "鲁肃", 23, 4, "计算机科学", "浙江大学")); add(new Student(20163001, "丁奉", 24, 5, "土木工程", "南京大学")); } };
过滤,顾名思义就是按照给定的要求对集合进行筛选知足条件的元素,java8提供的筛选操做包括:filter、distinct、limit、skip。性能
在前面的例子中咱们已经演示了如何使用filter,其定义为:Stream<T> filter(Predicate<? super T> predicate),filter接受一个谓词Predicate,咱们能够经过这个谓词定义筛选条件,在介绍lambda表达式时咱们介绍过Predicate是一个函数式接口,其包含一个test(T t)方法,该方法返回boolean。如今咱们但愿从集合students中筛选出全部武汉大学的学生,那么咱们能够经过filter来实现,并将筛选操做做为参数传递给filter:设计
List<Student> whStudents = students.stream().filter(student -> "武汉大学".equals(student.getSchool())).collect(Collectors.toList());
distinct操做相似于咱们在写SQL语句时,添加的DISTINCT关键字,用于去重处理,distinct基于Object。equals(Object)实现,回到最开始的例子,假设咱们但愿筛选出全部不重复的偶数,那么能够添加distinct操做:code
List<Integer> evens = nums.stream() .filter(num -> num % 2 == 0).distinct() .collect(Collectors.toList());
limit操做也相似于SQL语句中的LIMIT关键字,不过相对功能较弱,limit返回包含前n个元素的流,当集合大小小于n时,则返回实际长度,好比下面的例子返回前两个专业为土木工程专业的学生:blog
List<Student> whStudents = students.stream().filter(student -> "土木工程".equals(student.getMajor())).limit(2) .collect(Collectors.toList());
说到limit,不得不说起一下另一个流操做:sorted。该操做用于对流中元素进行排序,sorted要求待比较的元素必须实现Comparable接口,若是没有实现也没关系,咱们能够将比较器做为参数传递给sorted(Comparatoe<? super T> comparator),好比咱们但愿筛选出专业为土木工程的学生,并按年龄从小到大排序,筛选出年龄最小的两个学生,那么能够实现为
List<Student> whStudents = students.stream().filter(student -> "土木工程".equals(student.getMajor())) .sorted((s1,s2) -> s1.getAge()-s2.getAge()) .limit(2) .collect(Collectors.toList());
skip操做与limit操做相反,如同其字面意思同样,是跳过前n个元素,好比咱们但愿找出排序在2以后的土木工程专业的学生,那么能够实现为:
List<Student> whStudents = students.stream().filter(student -> "土木工程".equals(student.getMajor())) .skip(2) .collect(Collectors.toList());
经过skip,就会跳过前面两个元素,返回有后面全部元素构造的流,若是n大于知足条件的集合的长度,则会返回一个空的集合。
在SQL中,借助SELECT关键字后面添加须要的字段名称,能够仅输出咱们须要的字段数据,而流式处理的映射操做也是实现这一目的,在java8的流式处理中,主要包含两类映射操做:map和flatMap。
举例说明,假设咱们但愿筛选出全部专业为计算机科学的学生姓名,那么咱们能够在filter筛选的基础之上,经过map将学生实体映射成为学生姓名字符串,具体实现以下:
List<String> names = students.stream().filter(student -> "土木工程".equals(student.getMajor())) .map(Student::getName).collect(Collectors.toList());
除了上面这类基础的map,java8还提供了mapToDouble(ToDoubleFunction<? super T> mapper), mapToInt(ToIntFunction<? super T> mapper), mapToLong(ToLongFunction<? super T> mapper), 这些映射分别返回对应类型的流,java8为这么流设定了一些特殊的操做,好比咱们但愿计算全部专业为计算机科学学生的年龄之和,那么咱们能够实现以下:
int totalAge = students.stream().filter(student -> "计算机科学".equals(student.getMajor())) .mapToInt(Student::getAge).sum();
经过将Student按照年龄直接映射为IntStream,咱们能够直接调用提供的sum()方法来达到目的,此外使用这些数值流的好处还在于避免jvm装箱操做所带来的性能消耗。
终端操做是流式处理的最后一步,咱们能够在终端操做中实现对流查找、归约等操做。
allMatch用于检测是否所有都知足指定的参数行为,若是所有知足则返回true,假如咱们但愿检测是否全部的学生都已满18周岁,那么能够实现为:
boolean isAdult = students.stream().allMatch(student -> student.getAge() >= 18);
anyMatch则是检测是否存在一个或多个知足指定的参数行为,若是知足则返回true,假如咱们但愿检测是否有来自武汉大学的学生,那么能够实现为:
boolean isAdult = students.stream().anyMatch(student -> "武汉大学".equals(student.getSchool()));
noneMatch用于检测是否不存在知足执行行为的元素,若是不存在则返回true,假如咱们但愿检测是否不存在专业为计算机科学的学生,能够实现以下:
boolean isAdult = students.stream().noneMatch(student -> "计算机科学".equals(student.getMajor()));
findFirst用于返回知足条件的第一个元素,好比咱们但愿选出专业为土木工程的排在第一个学生,那么能够实现以下:
Optional<Student> optStudent = students.stream().filter(student -> "土木工程".equals(student.getMajor())).findFirst();
findFirst不携带参数,具体的查找条件能够经过filter设置,此外咱们能够发现findFirst返回的是一个Optional类型,关于该类型的具体讲解能够参考上一篇:Java8新特性-Optional类。
findAny相对于findFirst的区别在于,findAny不必定返回第一个,而是返回任意一个,好比咱们但愿返回任意一个专业为土木工程的学生,能够实现以下:
Optional<Student> optStudent = students.stream().filter(student -> "土木工程".equals(student.getMajor())).findAny();
实际上对于顺序流式处理而言,findFirst和findAny返回的结果是同样的,至于为何会这样设计,是由于在下一篇咱们介绍的并行流式处理,当咱们启用并行流式处理的时候,查找第一个元素每每会有不少限制,若是不是特别需求,在并行流式处理中使用findAny的性能要比findFirst好。
前面的例子中咱们大部分都是经过collect(Collectors.toList())对数据封装返回,如咱们的目标不是返回一个新的集合,而是但愿对通过参数化操做后的集合进行进一步的运算,那么咱们可用对集合实施归约的操做。java8的流式处理提供了reduce方法来达到这一目的。
前面咱们经过mapToInt将Stream<Student>映射成为IntStream,并经过IntStream的sum方法求得全部学生的年龄之和,实际上咱们经过归约操做,也能够达到这一目的,实现以下:
// 前面例子中的方法 int totalAge = students.stream() .filter(student -> "计算机科学".equals(student.getMajor())) .mapToInt(Student::getAge).sum(); // 归约操做 int totalAge = students.stream() .filter(student -> "计算机科学".equals(student.getMajor())) .map(Student::getAge) .reduce(0, (a, b) -> a + b); // 进一步简化 int totalAge2 = students.stream() .filter(student -> "计算机科学".equals(student.getMajor())) .map(Student::getAge) .reduce(0, Integer::sum); // 采用无初始值的重载版本,须要注意返回Optional Optional<Integer> totalAge = students.stream() .filter(student -> "计算机科学".equals(student.getMajor())) .map(Student::getAge) .reduce(Integer::sum); // 去掉初始值
前面利用collect(Collectors.toList())是一个简单的收集操做,是对处理结果的封装,对应的还有toSet、toMap,以知足咱们对于结果组织的需求。这些方法均来自于java.util.stream.Collectors,咱们能够称之为收集器。
收集器也提供了相应的概括操做,可是与reduce在内部实现上是有区别的,收集器更加适用于可变容器的概括操做,这些收集器广义上均基于Collectors.reducing()实现。
例1:求学生的总人数
long count = students.stream().collect(Collectors.counting()); //进一步简化 long count2 = students.stream().count();
例2:求年龄的最大值和最小值
//求最大年龄 Optional<Student> opStudent = students.stream().collect(Collectors.maxBy((s1,s2) -> s1.getAge()-s2.getAge())); //进一步简化 Optional<Student> opStudent2 = students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge))); //求最小年龄 Optional<Student> opStudent3 = students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge)));
例3:求年龄总和
int totalAge = students.stream().collect(Collectors.summingInt(Student::getAge));
对应的还有summingLong、summingDouble。
例4:求年龄的平均值