今天给大家带来 Java 8 Stream 讲解,为什么直接讲这个,是因为只要你学完,立刻就能上手,并能让它在你的代码中大展身手。
值得注意的是:学习 Stream 之前必须先学习 lambda 的相关知识。本文也假设读者已经掌握 lambda 的相关知识
本篇文章主要内容:
- 介绍 Stream 以及 Stream 是如何处理集合的
- 介绍 Stream 与集合的关系与区别
- Stream 的基本方法介绍
一. 什么是 Stream
Stream 中文称为 “流”,通过将集合转换为这么一种叫做 “流” 的元素序列,通过声明性方式,能够对集合中的每个元素进行一系列并行或串行的流水线操作。换句话说,你只需要告诉流你的要求和条件,流就会自行根据要求和条件对元素进行快速处理,从而得到你想要的结果
二. 流操作
其中数据源便是原始集合,然后将如 List<T> 的集合转换为 Stream<T> 类型的流,并对流进行一系列的中间操作,比如过滤保留部分元素、对元素进行排序、类型转换等;最后再进行一个终端操作,可以把 Stream 转换回集合类型,也可以直接对其中的各个元素进行处理,比如打印、比如计算总数、计算最大值等等
很重要的一点是,很多流操作本身就会返回一个流,所以多个操作可以直接连接起来,我们来看看一条 Stream 操作的代码:
四. 实战
首先创建一个 Student 对象
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Student implements Serializable {
/**
* 学成名字
*/
private String name;
/**
* 性别
*/
private String sex;
/**
* 年龄
*/
private Integer age;
/**
* 成绩分数
*/
private Integer grade;
/**
* 学科
*/
private String subject;
}
并且设置相应的值和集合数据
List<Student> studentList = new ArrayList<Student>(16) {{
add(new Student("张三", "男", 18, 85.5, "数学"));
add(new Student("李四", "男", 16, 92.5, "语文"));
add(new Student("张珊", "女", 16, 89.0, "英语"));
add(new Student("小明", "男", 15, 65.5, "数学"));
add(new Student("小红", "女", 17, 74.5, "语文"));
}};
1. stream() / parallelStream()
最常用到的方法,将集合转换为流
Stream<Student> studentListStream = studentList.stream();
而 parallelStream() 是并行流方法,能够让数据集执行并行操作
Stream<Student> studentListParallelStream = studentList.parallelStream();
2. filter()
保留 boolean 为 true 的元素
// 过滤保留年龄大于17的学生
List<Student> students = studentList.stream().filter(s -> 17 < s.getAge()).collect(Collectors.toList());
打印输出:[Student(name=张三, sex=男, age=18, grade=85.5, subject=数学)]
3. distinct()
去除重复元素,这个方法是通过类的 equals 方法来判断两个元素是否相等的
// 过滤保留年龄大于17岁,去除相同名字的学生
List<String> students = studentList.stream().filter(s -> 17 == s.getAge()).map(Student::getName).distinct().collect(Collectors.toList());
打印输出: [小红]
4. sorted()
如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,需要自定义调用 sorted((T1, T2) -> return int)
实现 Comparator 接口
调用sorted()
// 年龄自然排序正序
List<Integer> students = studentList.stream().map(Student::getAge).sorted().collect(Collectors.toList());
打印输出:[15, 16, 16, 17, 17, 18]
调用sorted((T1, T2) -> return int)
// 年龄自然排序正序
List<Student> students = studentList.stream().sorted(Comparator.comparing(Student::getAge)).collect(Collectors.toList());
// 或者:
List<Student> students = studentList.stream().sorted((t1, t2)-> t1.getAge() - t2.getAge()).collect(Collectors.toList());
打印输出:[Student(name=小明, sex=男, age=15, grade=65.5, subject=数学), Student(name=李四, sex=男, age=16, grade=92.5, subject=语文), Student(name=张珊, sex=女, age=16, grade=89.0, subject=英语), Student(name=小红, sex=女, age=17, grade=74.5, subject=语文), Student(name=小红, sex=女, age=17, grade=71.5, subject=英语), Student(name=张三, sex=男, age=18, grade=85.5, subject=数学)]
5. limit()
截取返回前 n 个元素
// 截取返回前 2 个学生
List<Student> students = studentList.stream().limit(2).collect(Collectors.toList());
打印输出:[Student(name=张三, sex=男, age=18, grade=85.5, subject=数学), Student(name=李四, sex=男, age=16, grade=92.5, subject=语文)]
6. skip()
去除(跳过)前 n 个元素
List<Student> students = studentList.stream().skip(4).collect(Collectors.toList());
打印输出: [Student(name=小红, sex=女, age=17, grade=74.5, subject=语文), Student(name=小红, sex=女, age=17, grade=71.5, subject=英语)]
注意事项:
- skip(m)用在 limit(n) 前面时,先去除前 m 个元素再返回剩余元素的前 n 个元素
- limit(n) 用在 skip(m) 前面时,先返回前 n 个元素再在剩余的 n 个元素中去除 m 个元素
List<Student> students = studentList.stream().skip(2).limit(1).collect(Collectors.toList());
List<Student> students = studentList.stream().limit(2).skip(1).collect(Collectors.toList());
打印输出1: [Student(name=张珊, sex=女, age=16, grade=89.0, subject=英语)]
打印输出2: [Student(name=李四, sex=男, age=16, grade=92.5, subject=语文)]
7. map()
将流中的每一个元素 T 映射为 R(类似类型转换, 可以转换为任意类型)
// 输出人名
List<String> students = studentList.stream().map(Student::getName).collect(Collectors.toList());
// 将所有人的分数加5分
List<Double> students = studentList.stream().map(s -> s.getGrade() + 5.0D).collect(Collectors.toList());
打印输出1:[张三, 李四, 张珊, 小明, 小红, 小红]
打印输出2:[90.5, 97.5, 94.0, 70.5, 79.5, 76.5]
newlist 里面的元素为 list 中每一个 Person 对象的 name 变量
8. flatMap()
将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流
List<String> students = studentList.stream().map(s-> s.getName().split("")).flatMap(Arrays::stream).collect(Collectors.toList());
打印输出:[张, 三, 李, 四, 张, 珊, 小, 明, 小, 红, 小, 红]
上面例子中,我们的目的是把 studentList 中每个人名字符串元素以" "分割开,变成一个新的 List<String>。
首先 map 方法分割每个字符串元素,但此时流的类型为 Stream<String[ ]>,因为 split 方法返回的是 String[ ] 类型;所以我们需要使用 flatMap 方法,先使用Arrays::stream
将每个 String[ ] 元素变成一个 Stream<String> 流,然后 flatMap 会将每一个流连接成为一个流,最终返回我们需要的 Stream<String>
9. anyMatch()
流中是否有一个元素匹配给定的 T -> boolean
条件
// 是否存在一个student对象的年龄等于18:
boolean b = studentList.stream().anyMatch(s -> 18 == s.getAge());
打印输出:true
10. allMatch()
流中是否所有元素都匹配给定的 T -> boolean
条件
boolean b = studentList.stream().allMatch(s -> 18 == s.getAge());
打印输出: false
11. noneMatch()
流中是否没有元素匹配给定的 T -> boolean
条件
boolean b = studentList.stream().noneMatch(s -> 18 == s.getAge());
打印输出:false
12. findAny() 和 findFirst()
- findAny():找到其中一个元素 (使用 stream() 时找到的是第一个元素;使用 parallelStream() 并行时找到的是其中一个元素)
- findFirst():找到第一个元素
Student student = studentList.stream().findFirst().get();
打印输出:Student(name=张三, sex=男, age=18, grade=85.5, subject=数学)
Student student = studentList.stream().findAny().get();
打印输出:Student(name=张三, sex=男, age=18, grade=85.5, subject=数学)
值得注意的是,这两个方法返回的是一个 Optional<T> 对象,它是一个容器类,能代表一个值存在或不存在,这个后面会讲到
14. reduce() 和 reduce()
用于组合流中的元素,如求和,求积,求最大值等
// 计算年龄总和:
Integer ages = studentList.stream().map(Student::getAge).reduce(Integer::sum).get();
// 或者:
Integer ages = studentList.stream().map(Student::getAge).reduce(0, Integer::sum);
其中,reduce 第一个参数 0 代表起始值为 0,lambda (a, b) -> a + b
即将两值相加产生一个新值:
// 计算年龄总乘积:
Integer ages = studentList.stream().map(Student::getAge).reduce(0, (t1, t2) -> t1 * t2);
15. count()
返回流中元素个数,结果为 long 类型
long count = studentList.stream().map(Student::getAge).count();
16. collect()
收集方法,我们很常用的是 collect(toList())
,当然还有 collect(toSet())
等,参数是一个收集器接口,这个后面会另外讲
List<Student> students = studentList.stream().unordered().collect(Collectors.toList());
17. forEach()
返回结果为 void,很明显我们可以通过它来干什么了,比方说:
// unordered() 还有这个比较不起眼的方法,返回一个等效的无序流,当然如果流本身就是无序的话,那可能就会直接返回其本身
// 打印各个元素:
studentList.forEach(System.out::println);
再比如说 MyBatis 里面访问数据库的 mapper 方法:
// 向数据库插入新元素:
studentList.stream().forEach(StudentMapper::insert);