引入流
流是什么
流是Java API的新成员,它允许你以声明性的方式处理数据集合。可以看成遍历数据集的高级迭代。流可以透明地并行处理,无需编写多线程代码。我们先简单看一下使用流的好处。下面两段代码都是用来返回年龄小于14岁的初中生的姓名,并按照年龄排序。
假如我们有下面Student
实体类
@Data
public class Student {
private String name;
private int age;
private boolean member;
private Grade grade;
public Student() {
}
public Student(String name, int age, boolean member, Grade grade) {
this.name = name;
this.age = age;
this.member = member;
this.grade = grade;
}
public enum Grade{
JUNIOR_ONE,JUNIOR_TWO,JUNIOR_THREE
}
}
Java 8之前的实现方式:
List<Student> students = Arrays.asList(
new Student("张初一", 13, false, Student.Grade.JUNIOR_ONE),
new Student("李初二", 14, false, Student.Grade.JUNIOR_TWO),
new Student("孙初三", 15, false, Student.Grade.JUNIOR_THREE),
new Student("王初一", 12, false, Student.Grade.JUNIOR_ONE),
new Student("钱初二", 14, false, Student.Grade.JUNIOR_TWO),
new Student("周初三", 16, false, Student.Grade.JUNIOR_THREE));
List<Student> resultStudent = new ArrayList<>(); //垃圾变量,一次性的中间变量
//foreach循环,根据条件筛选元素
for (Student student : students) {
if (student.getAge() < 14) {
resultStudent.add(student);
}
}
//匿名类,给元素排序
Collections.sort(resultStudent, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Integer.compare(o1.getAge(), o2.getAge());
}
});
List<String> resultName = new ArrayList<>();
//foreach循环,获取元素属性
for (Student student : resultStudent) {
resultName.add(student.getName());
}
Java 8流的实现方式:
List<String> resultName = students.stream()
.filter(student -> student.getAge() < 14) //年龄筛选
.sorted(Comparator.comparing(Student::getAge)) //年龄排序
.map(Student::getName) //提取姓名
.collect(Collectors.toList());//将提取的姓名保存在List中
为了利用多核架构并行执行这段代码,只需要把stream()
替换成parallelStream()
即可。
通过对比两段代码之后,Java8流的方式有几个显而易见的好处。
- 代码是以声明性的方式写的:说明想要做什么(筛选小于14岁的学生)而不是去说明怎么去做(循环、if)
- 将几个基础操作链接起来,表达复杂的数据处理流水线(filter->sorted->map->collect),同时保持代码清晰可读。
总结一下,Java 8的Stream API带来的好处:
- 声明性-更简洁,更易读
- 可复合-更灵活
- 可并行-性能更好
流简介
流到底是什么?简单定义:“从支持数据处理操作的源生成的元素序列”,下面剖析这个定义。
- 元素序列:像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。集合讲的是数据,流讲的是计算。
- 源:流使用一个提供数据的源,如集合、数组或输入/输出资源。
- 数据处理操作:流的数据处理功能之处类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。流的操作可以顺序执行,也可以并行执行。
- 流水线:很多流的操作会返回一个流,这样多个操作就可以链接起来,形成一个流水线。可以看成数据库式查询。
- 内部迭代:于迭代器显示迭代的不同,流的迭代操作是在背后进行的。
看一段代码,更好理解这些概念
List<String> resultName = students.stream() //从列表中获取流
.filter(student -> student.getAge() < 16) //操作流水线:筛选
.map(Student::getName) //操作流水线:提取姓名
.limit(2) //操作流水线:只取2个
.collect(Collectors.toList());//将结果保存在List中
在上面代码中,数据源是学生列表(students),用来给流提供一个元素序列,调用stream()
获取一个流,接下来就是一系列数据处理操作:filter、map、limit和collect。除collect之外,所有这些操作都会返回一个流,组成了一条流水线。最后collect操作开始处理流水线,并返回结果。
流与集合
粗略的说,流与集合之间的差异就在于什么时候进行计算。
- 集合是一个内存中的数据结构(可以添加或者删除),它包含数据结构中目前所有的值——集合中的每个元素都是预先处理好然后添加到集合中的。
- 流则是在概念上固定的数据结构(不能添加或删除元素),其元素是按需计算的。
在哲学中,流被看作在时间中分布的一组值,而集合则是空间(计算机内存)中分布的一组值,在一个时间点上全体存在。
只能遍历一次
和迭代器类似,流只能遍历一次。遍历完成之后,我们说这个流已经被消费掉了。
例如下面的代码会抛出异常
Stream<Student> stream = students.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println);
执行之后抛出