java8之流的基本使用(二)
概述
流(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方法將流中所有的元素叠代合成一個結果,可以求出最大值或最小值
java8之流的基本使用(二)