java8之流的基本使用(二)
java8之流的基本使用(二)
概述
流(stream())是java8的一个新特性,主要的作用就是将各种类型的集合转换为流,然后的方便迭代数据用的.例如:
//将List类型的集合转换为流
list.stream()
转换为流之后可以进行一系列的迭代操作,比自己去拿出list的值一个个操作要方便的多.
使用流的好处
- 声明性 — 更简洁、更易读
- 可复合 — 更灵活
- 可并行 — 性能更好
流的使用方法介绍
使用流之前,必须先对函数式接口、lamda表达式和方法引用有一些了解,如果您不具备这方面知识,请移驾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方法将流中所有的元素迭代合成一个结果,可以求出最大值或最小值