概述
流 (stream()) 是 java8 的一个新特性, 主要的作用就是将各种类型的集合转换为流, 然后的方便迭代数据用的. 例如:
- // 将 List 类型的集合转换为流
- list.stream()
转换为流之后可以进行一系列的迭代操作, 比自己去拿出 list 的值一个个操作要方便的多.
使用流的好处
声明性 -- 更简洁, 更易读
可复合 -- 更灵活
可并行 -- 性能更好
流的使用方法介绍
使用流之前, 必须先对函数式接口, lambda 表达式和方法引用有一些了解, 如果您不具备这方面知识, 请移驾 lambda 表达式 & 方法引用.
使用的 oracle 默认的 emp 表的字段:
- public class Emp {
- private BigDecimal empno;
- private boolean trueOrFalse;
- private String ename;
- private String job;
- private BigDecimal mgr;
- private Date hiredate;
- private Double sal;
- private BigDecimal comm;
- private BigDecimal deptno;
- public BigDecimal getEmpno() {
- return empno;
- }
- public void setEmpno(BigDecimal empno) {
- this.empno = empno;
- }
- public String getEname() {
- return ename;
- }
- public void setEname(String ename) {
- this.ename = ename == null ? null : ename.trim();
- }
- public String getJob() {
- return job;
- }
- public void setJob(String job) {
- this.job = job == null ? null : job.trim();
- }
- public BigDecimal getMgr() {
- return mgr;
- }
- public void setMgr(BigDecimal mgr) {
- this.mgr = mgr;
- }
- public Date getHiredate() {
- return hiredate;
- }
- public void setHiredate(Date hiredate) {
- this.hiredate = hiredate;
- }
- public Double getSal() {
- return sal;
- }
- public void setSal(Double sal) {
- this.sal = sal;
- }
- public BigDecimal getComm() {
- return comm;
- }
- public void setComm(BigDecimal comm) {
- this.comm = comm;
- }
- public BigDecimal getDeptno() {
- return deptno;
- }
- public void setDeptno(BigDecimal deptno) {
- this.deptno = deptno;
- }
- }
1. 过滤
得到工资在 1000 以上的员工的集合:
- // 得到 list 集合
- List<Emp> listEmp = empService.listEmp();
- /*
- * 1. listEmp.stream() 将集合转换为流,
- * 这样就可以用流的方法对集合进行迭代.
- *
- * 2.filter 方法. 里面的 emp 相当于拿到集合中的每一个 emp 进行操作,
- * 结果要返回一个 Boolean 值
- *
- * 3. .collect(toList()), 实际是. collect(Collectors.toList()).
- * */
- List<Emp> result = listEmp.stream().filter(emp -> emp.getSal()> 1000).collect(toList());
- System.out.println("result =" + result);
打印输出:
result = [Emp(empno=7499, trueOrFalse=false, ename=ALLEN, job=SALESMAN, mgr=7698, hiredate=Fri Feb 20 00:00:00 CST 1981, sal=1600.0, comm=300, deptno=30), Emp(empno=7521, trueOrFalse=false, ename=WARD, job=SALESMAN, mgr=7698, hiredate=Sun Feb 22 00:00:00 CST 1981, sal=1250.0, comm=500, deptno=30), Emp(empno=7566, trueOrFalse=false, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20), Emp(empno=7654, trueOrFalse=false, ename=MARTIN, job=SALESMAN, mgr=7698, hiredate=Mon Sep 28 00:00:00 CST 1981, sal=1250.0, comm=1400, deptno=30), Emp(empno=7698, trueOrFalse=false, ename=BLAKE, job=MANAGER, mgr=7839, hiredate=Fri May 01 00:00:00 CST 1981, sal=2850.0, comm=null, deptno=30), Emp(empno=7782, trueOrFalse=false, ename=CLARK, job=MANAGER, mgr=7839, hiredate=Tue Jun 09 00:00:00 CST 1981, sal=2450.0, comm=null, deptno=10), Emp(empno=7788, trueOrFalse=false, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=Sun Apr 19 00:00:00 CDT 1987, sal=3000.0, comm=null, deptno=20), Emp(empno=7839, trueOrFalse=false, ename=KING, job=PRESIDENT, mgr=null, hiredate=Tue Nov 17 00:00:00 CST 1981, sal=5000.0, comm=null, deptno=10), Emp(empno=7844, trueOrFalse=false, ename=TURNER, job=SALESMAN, mgr=7698, hiredate=Tue Sep 08 00:00:00 CST 1981, sal=1500.0, comm=0, deptno=30), Emp(empno=7876, trueOrFalse=false, ename=ADAMS, job=CLERK, mgr=7788, hiredate=Sat May 23 00:00:00 CDT 1987, sal=1100.0, comm=null, deptno=20), Emp(empno=7902, trueOrFalse=false, ename=FORD, job=ANALYST, mgr=7566, hiredate=Thu Dec 03 00:00:00 CST 1981, sal=3000.0, comm=null, deptno=20), Emp(empno=7934, trueOrFalse=false, ename=MILLER, job=CLERK, mgr=7782, hiredate=Sat Jan 23 00:00:00 CST 1982, sal=1300.0, comm=null, deptno=10)]
结果返回了所有工资在 1000 以上的员工.
分布介绍一下方法,.filter()会对集合中的每一个元素都执行一次括号内的操作:
.filter(emp -> emp.getSal()> 1000)
我们追一下 filter 方法, 可以看到:
Stream<T> filter(Predicate<? super T> predicate);
返回的是一个流, 参数是一个 Predicate. 再追这个参数:
- @FunctionalInterface
- public interface Predicate<T> {
- /**
- * Evaluates this predicate on the given argument.
- *
- * @param t the input argument
- * @return {@code true} if the input argument matches the predicate,
- * otherwise {@code false}
- */
- boolean test(T t);
@FunctionalInterface 注解说明这是一个函数式接口. 并不是有了这个注释才说明这是一个函数式接口, 而是只有一个抽象方法的接口, 就是函数式接口.
接口的方法:
boolean test(T t);
参数是任意类型, 返回值 Boolean 类型.
也就是说. filter()方法, 可以传递任意类型的参数, 返回值必须是 Boolean 类型的, 只要满足这两个条件, 都可以传到 filter 方法中.
我们传递的, 参数 emp, 返回值 emp.getSal()>1000 是个 Boolean 值, 所以满足:
.filter(emp -> emp.getSal()> 1000)
.collect(toList())将结果转换为一个 list 集合, 真正地写法为:
.collect(Collectors.toList());
因为我在这个类中使用了静态导入:
import static java.util.stream.Collectors.*;
所以前面的 Collectors 可以不写.
2. 截断
有时候, 得到一个集合只需要其中的几位, 这时候可以使用截断:
- List<Emp> result = listEmp.stream()
- .filter(emp -> emp.getSal()> 1000)
- // 截取 3 位
- .limit(3)
- .collect(toList());
- System.out.println("result =" + result);
打印输出: result = [Emp(empno=7499, trueOrFalse=false, ename=ALLEN, job=SALESMAN, mgr=7698, hiredate=Fri Feb 20 00:00:00 CST 1981, sal=1600.0, comm=300, deptno=30), Emp(empno=7521, trueOrFalse=false, ename=WARD, job=SALESMAN, mgr=7698, hiredate=Sun Feb 22 00:00:00 CST 1981, sal=1250.0, comm=500, deptno=30), Emp(empno=7566, trueOrFalse=false, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20)]
.limit()方法, 可以截取指定的位数
3. 跳过元素
刚刚是截断 3 位, 这次我们输出跳过 3 位的结果, 使用. skip():
- List<Emp> result = listEmp.stream()
- .filter(emp -> emp.getSal()> 1000)
- // 跳过 3 位
- .skip(3)
- .collect(toList());
- System.out.println("result =" + result);
打印输出: result = [Emp(empno=7654, trueOrFalse=false, ename=MARTIN, job=SALESMAN, mgr=7698, hiredate=Mon Sep 28 00:00:00 CST 1981, sal=1250.0, comm=1400, deptno=30), Emp(empno=7698, trueOrFalse=false, ename=BLAKE, job=MANAGER, mgr=7839, hiredate=Fri May 01 00:00:00 CST 1981, sal=2850.0, comm=null, deptno=30), Emp(empno=7782, trueOrFalse=false, ename=CLARK, job=MANAGER, mgr=7839, hiredate=Tue Jun 09 00:00:00 CST 1981, sal=2450.0, comm=null, deptno=10), Emp(empno=7788, trueOrFalse=false, ename=SCOTT, job=ANALYST, mgr=7566, hiredate=Sun Apr 19 00:00:00 CDT 1987, sal=3000.0, comm=null, deptno=20), Emp(empno=7839, trueOrFalse=false, ename=KING, job=PRESIDENT, mgr=null, hiredate=Tue Nov 17 00:00:00 CST 1981, sal=5000.0, comm=null, deptno=10), Emp(empno=7844, trueOrFalse=false, ename=TURNER, job=SALESMAN, mgr=7698, hiredate=Tue Sep 08 00:00:00 CST 1981, sal=1500.0, comm=0, deptno=30), Emp(empno=7876, trueOrFalse=false, ename=ADAMS, job=CLERK, mgr=7788, hiredate=Sat May 23 00:00:00 CDT 1987, sal=1100.0, comm=null, deptno=20), Emp(empno=7902, trueOrFalse=false, ename=FORD, job=ANALYST, mgr=7566, hiredate=Thu Dec 03 00:00:00 CST 1981, sal=3000.0, comm=null, deptno=20), Emp(empno=7934, trueOrFalse=false, ename=MILLER, job=CLERK, mgr=7782, hiredate=Sat Jan 23 00:00:00 CST 1982, sal=1300.0, comm=null, deptno=10)]
4. 映射
.map()方法, 得到集合中每个元素的某个信息时使用, 比如我们只要拿到集合中的所有员工的姓名:
- List<Emp> listEmp = empService.listEmp();
- List<String> resultList = listEmp.stream()
- // 得到集合中的某一个元素
- .map(emp -> emp.getEname())
- .collect(toList());
- System.out.println("resultList =" + resultList);
打印输出:
resultList = [aaa, SMITH, ALLEN, WARD, JONES, MARTIN, BLAKE, CLARK, SCOTT, KING, TURNER, ADAMS, JAMES, FORD, MILLER]
当你不知道流中的方法,.filter()或者是. map()抑或是其他任何流中的方法里面需要传递什么参数返回什么结果的时候, 就可以向之前一样, 追踪一下源码, 我们以. map()为例:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
map 方法里面需要的是另一种函数式接口, Function, 我们继续追:
- @FunctionalInterface
- public interface Function<T, R> {
- /**
- * Applies this function to the given argument.
- *
- * @param t the function argument
- * @return the function result
- */
- R apply(T t);
可以看到里面的 apply 方法, 是传入任意类型的 T, 返回的是不同的任意类型的 R, 在看我们的代码, 传入 Emp, 返回 String, 满足!:
.map(emp -> emp.getEname())
5. 扁平的映射
就是. flatMap()方法. 这个方法的作用是将一个流中的每个流都换成另一个值, 然后把所有流连接起来成为一个流.
举个具体的例子. 将["Hello","World"] 变为["H","e","l","w","r","d"]
- List<String> collect = list.stream()
- .map(txt -> txt.split(""))
- .flatMap(txt -> Arrays.stream(txt))
- .distinct().collect(toList());
使用. split("")方法返回两个数组 {"H","e","l","l","o"} 和{"W","o""r","d"}
.map(txt -> txt.split(""))
Arrays.stream()方法将两个数组变成两个流, flatMap 将两个流合成一个流.
.flatMap(txt -> Arrays.stream(txt))
6. 查找和匹配
Stream API 提供 allMatch,anyMatch,noneMatch,findFirst 和 findAny 方法. 判断集合中是否有要匹配的值.
匹配
anyMatch, 如果有一个匹配就返回 true, 看看是否有员工叫 SMITH:
- List<Emp> list = empService.listEmp();
- boolean result = list.stream().anyMatch(emp -> emp.getEname().equals("SMITH"));
- System.out.println("result =" + result);
输出结果: result = true
allMatch, 全部匹配才返回 true:
- List<Emp> list = empService.listEmp();
- boolean result = list.stream().allMatch(emp -> emp.getEname().equals("SMITH"));
- System.out.println("result =" + result);
输出: result = false
noneMatch, 如果没有匹配到返回 true:
- List<Emp> list = empService.listEmp();
- boolean result = list.stream().noneMatch(emp -> emp.getEname().equals("SMITH"));
- System.out.println("result =" + result);
打印输出: result = false
anyMatch,allMatch 和 noneMatch 这三个操作都用到了我们所谓的短路, 这就是大家熟悉的 Java 中的 && 和 || 运算符短路在流中的版本.
这里要说明一点, stream 分为中间操作和终端操作, 像 map(),filter(),flatMap()等就是中间操作, 他们可以继续调用其它中间操作, 想一个流水线一样. 而终端操作就是无法再调用其它流的方法的方法, 例如 刚刚的三个 match 方法和 collect()方法等.
查找
findAny 方法返回当前流中的任意元素, 可以将 filter 和 findAny 配合使用:
- List<Emp> list = empService.listEmp();
- Optional<Emp> result = list.stream().filter(emp -> emp.getSal()> 2000).findAny();
- System.out.println("result =" + result);
- System.out.println("result.get() =" + result.get());
打印输出:
- result = Optional[Emp(empno=7566, trueOrFalse=false, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20)]
- result.get() = Emp(empno=7566, trueOrFalse=false, ename=JONES, job=MANAGER, mgr=7839, hiredate=Thu Apr 02 00:00:00 CST 1981, sal=2975.0, comm=null, deptno=20)
细心的朋友可能发现了, 这里返回的结果是一个:
Optional<Emp> result
有了 Optional 类以后就可以和 nullPointerException 说 88 了, 这是一个容器类, 代表一个值存在或不存在
findFirst 方法和 findAny 差不多, 但是如果这是一个并行流, findAny 返回的就是最先找到的任意一个, 而 findFirst 返回的是第一个.
7. 归约
reduce()方法, 求所有员工的工资之和:
- List<Emp> list = empService.listEmp();
- Double reduce = list.stream().map(emp -> emp.getSal())
- // 这里的第一个参数 0 代表初始值
- .reduce((double) 0, (a, b) -> a + b);
- System.out.println("得到的工资之和是:"+reduce);
打印输出: 得到的工资之和是: 29026.0
注意 reduce()方法中的第一个参数 0 也可以不写但是返回的就是一个 Optional 类型的结果.
- List<Emp> list = empService.listEmp();
- Optional<Double> reduce = list.stream().map(emp -> emp.getSal())
- // 这里的第一个参数 0 代表初始值
- .reduce((a, b) -> a + b);
- System.out.println("得到的工资之和是:"+reduce);
还有一点需要注意的是, 如果 reduce 执行的是乘法, 那么初始值就应该是 1 而不是 0.
8. 最大值和最小值
使用 reduce 也可以获得最大值和最小值:
- List<Emp> list = empService.listEmp();
- Optional<Double> reduce = list.stream().map(emp -> emp.getSal())
- // 获得最大值
- .reduce(Double::max);
- System.out.println("最大工资是::"+reduce.get());
打印输出: 最大工资是::5000.0
求最小值只需把 Double::max 换成 Double::min.(这是方法引用, 看不懂的移步上面的另一篇博客)
9 由值创建流
使用 Stream.of 方法创建一个流.
- Stream<String> values = Stream.of("aaa","bbb","ccc");
- values.map(String::toUpperCase).forEach(System.out::println);
- // 等价于下面的
- values.map(value -> value.toUpperCase()).forEach(value -> System.out.println(value));
两个写法的效果是一样的, 一个使用的是 lambda 表达式一个使用的是方法引用.
10. 由数组创建流
- int[] values = {1,2,3};
- IntStream stream = Arrays.stream(values);
- //sum 方法求和
- int sum = stream.sum();
- System.out.println("sum =" + sum);
打印输出: sum = 6
11. 由函数生成流: 创建无限流
Stream.iterate 和 Stream.generate 可以创建无穷无尽的流, 但是一般会使用 limit()方法来限制创建流的大小, 以避免打印无数的值, 例子:
- Stream.iterate(0,n-> n+2)
- .limit(10)
- .forEach(System.out::println);
输出:
- 0
- 2
- 4
- 6
- 8
- 10
- 12
- 14
- 16
- 18
第一次输出的是 0, 共输出十次.
generate 与 iterate 类似.
总结
可以使用 filter,distinct,skip 和 limit 对流做筛选和切片.
使用 map 和 flatMap 提取或转换流中的元素.
使用 findFirst 和 findAny 方法查找流中的元素. 可以用 allMatch,noneMatch,anyMatch 方法让流匹配给定的谓词.
这些方法都利用了短路: 找到结果就立即停止计算; 没有必要处理整个流.
使用 reduce 方法将流中所有的元素迭代合成一个结果, 可以求出最大值或最小值
来源: https://www.cnblogs.com/xisuo/p/10042189.html