Stream流的使用
流操作是Java8提供一个重要新特性,它允许开发人员以声明性方式处理集合,其核心类库主要改进了对集合类的 API和新增Stream操作。Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中,能 让代码更加简洁,极大地简化了集合的处理操作,提高了开发的效率和生产力。
同时stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。在Stream中的操作每一次都会产生新的流,内部不会像普通集合操作一样立刻获取值,而是惰性 取值,只有等到用户真正需要结果的时候才会执行。并且对于现在调用的方法,本身都是一种高层次构件,与线程模型无关。因此在并行使用中,开发者们无需再去操 心线程和锁了。Stream内部都已经做好了。
如果刚接触流操作的话,可能会感觉不太舒服。其实理解流操作的话可以对比数据库操作。把流的操作理解为对数据库中 数据的查询操作
集合 = 数据表
元素 = 表中的每条数据
属性 = 每条数据的列
流API = sql查询
流操作详解
Stream流接口中定义了许多对于集合的操作方法,总的来说可以分为两大类:中间操作和终端操作。
-
中间操作:会返回一个流,通过这种方式可以将多个中间操作连接起来,形成一个调用链,从而转换为另外 一个流。除非调用链后存在一个终端操作,否则中间操作对流不会进行任何结果处理。
-
终端操作:会返回一个具体的结果,如boolean、list、integer等。
1、筛选
对于集合的操作,经常性的会涉及到对于集中符合条件的数据筛选,Stream中对于数据筛选两个常见的API: filter(过滤)、distinct(去重)
1.1基于filter()实现数据过
该方法会接收一个返回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、切片
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,则获取已知集合大小长度-跳过的长度。最终进行数据切割。
3、映射
在对集合进行操作的时候,我们经常会从某些对象中选择性的提取某些元素的值,就像编写sql一样,指定获取表 中特定的数据列
#指定获取特定列 SELECT name FROM student
在Stream API中也提供了类似的方法,map()。它接收一个函数作为方法参数,这个函数会被应用到集合中每一个 元素上,并终将其映射为一个新的元素。
案例:获取所有学生的姓名,并形成一个新的集合
/**
*