上一篇文章我講解 Stream 流的基本原理,以及它與集合的區別關係,講了那麼多抽象的,本篇文章咱們開始實戰,講解流的各個方法以及各類操做程序員
沒有看過上篇文章的能夠先點擊進去學習一下 簡潔又快速地處理集合——Java8 Stream(上),固然你直接看這篇也能夠,不過了解其自己才能更融會貫通哦。數據庫
值得注意的是:學習 Stream 以前必須先學習 lambda 的相關知識。本文也假設讀者已經掌握 lambda 的相關知識。編程
首先咱們先建立一個 Person 泛型的 List後端
List<Person> list = new ArrayList<>(); list.add(new Person("jack", 20)); list.add(new Person("mike", 25)); list.add(new Person("tom", 30));
Person 類包含年齡和姓名兩個成員變量數組
private String name; private int age;
最經常使用到的方法,將集合轉換爲流數據結構
List list = new ArrayList(); // return Stream<E> list.stream();
而 parallelStream() 是並行流方法,可以讓數據集執行並行操做,後面會更詳細地講解app
保留 boolean 爲 true 的元素dom
保留年齡爲 20 的 person 元素 list = list.stream() .filter(person -> person.getAge() == 20) .collect(toList()); 打印輸出 [Person{name='jack', age=20}]
collect(toList()) 能夠把流轉換爲 List 類型,這個之後會講解函數式編程
去除重複元素,這個方法是經過類的 equals 方法來判斷兩個元素是否相等的函數
如例子中的 Person 類,須要先定義好 equals 方法,否則相似[Person{name='jack', age=20}, Person{name='jack', age=20}]
這樣的狀況是不會處理的
若是流中的元素的類實現了 Comparable 接口,即有本身的排序規則,那麼能夠直接調用 sorted() 方法對元素進行排序,如 Stream
反之, 須要調用 sorted((T, T) -> int)
實現 Comparator 接口
根據年齡大小來比較: list = list.stream() .sorted((p1, p2) -> p1.getAge() - p2.getAge()) .collect(toList());
固然這個能夠簡化爲
list = list.stream() .sorted(Comparator.comparingInt(Person::getAge)) .collect(toList());
返回前 n 個元素
list = list.stream() .limit(2) .collect(toList()); 打印輸出 [Person{name='jack', age=20}, Person{name='mike', age=25}]
去除前 n 個元素
list = list.stream() .skip(2) .collect(toList()); 打印輸出 [Person{name='tom', age=30}]
tips:
list = list.stream() .limit(2) .skip(1) .collect(toList()); 打印輸出 [Person{name='mike', age=25}]
將流中的每個元素 T 映射爲 R(相似類型轉換)
List<String> newlist = list.stream().map(Person::getName).collect(toList());
newlist 裏面的元素爲 list 中每個 Person 對象的 name 變量
將流中的每個元素 T 映射爲一個流,再把每個流鏈接成爲一個流
List<String> list = new ArrayList<>(); list.add("aaa bbb ccc"); list.add("ddd eee fff"); list.add("ggg hhh iii"); list = list.stream().map(s -> s.split(" ")).flatMap(Arrays::stream).collect(toList());
上面例子中,咱們的目的是把 List 中每一個字符串元素以" "分割開,變成一個新的 List
首先 map 方法分割每一個字符串元素,但此時流的類型爲 Stream<String[ ]>,由於 split 方法返回的是 String[ ] 類型;因此咱們須要使用 flatMap 方法,先使用
Arrays::stream
將每一個 String[ ] 元素變成一個 Stream
流中是否有一個元素匹配給定的 T -> boolean
條件
是否存在一個 person 對象的 age 等於 20: boolean b = list.stream().anyMatch(person -> person.getAge() == 20);
流中是否全部元素都匹配給定的 T -> boolean
條件
流中是否沒有元素匹配給定的 T -> boolean
條件
值得注意的是,這兩個方法返回的是一個 Optional
用於組合流中的元素,如求和,求積,求最大值等
計算年齡總和: int sum = list.stream().map(Person::getAge).reduce(0, (a, b) -> a + b); 與之相同: int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum);
其中,reduce 第一個參數 0 表明起始值爲 0,lambda (a, b) -> a + b
即將兩值相加產生一個新值
一樣地:
計算年齡總乘積: int sum = list.stream().map(Person::getAge).reduce(1, (a, b) -> a * b);
固然也能夠
Optional<Integer> sum = list.stream().map(Person::getAge).reduce(Integer::sum);
即不接受任何起始值,但由於沒有初始值,須要考慮結果可能不存在的狀況,所以返回的是 Optional 類型
返回流中元素個數,結果爲 long 類型
收集方法,咱們很經常使用的是 collect(toList())
,固然還有 collect(toSet())
等,參數是一個收集器接口,這個後面會另外講
返回結果爲 void,很明顯咱們能夠經過它來幹什麼了,比方說:
### 16. unordered() 還有這個比較不起眼的方法,返回一個等效的無序流,固然若是流自己就是無序的話,那可能就會直接返回其自己 打印各個元素: list.stream().forEach(System.out::println);
再好比說 MyBatis 裏面訪問數據庫的 mapper 方法:
向數據庫插入新元素: list.stream().forEach(PersonMapper::insertPerson);
前面介紹的如
int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum);
計算元素總和的方法其中暗含了裝箱成本,map(Person::getAge)
方法事後流變成了 Stream
針對這個問題 Java 8 有良心地引入了數值流 IntStream, DoubleStream, LongStream,這種流中的元素都是原始數據類型,分別是 int,double,long
IntStream intStream = list.stream().mapToInt(Person::getAge);
固然若是是下面這樣便會出錯
LongStream longStream = list.stream().mapToInt(Person::getAge);
由於 getAge 方法返回的是 int 類型(返回的若是是 Integer,同樣能夠轉換爲 IntStream)
很簡單,就一個 boxed
Stream<Integer> stream = intStream.boxed();
下面這些方法做用不用多說,看名字就知道:
IntStream 與 LongStream 擁有 range 和 rangeClosed 方法用於數值範圍處理
這兩個方法的區別在於一個是閉區間,一個是半開半閉區間:
咱們能夠利用 IntStream.rangeClosed(1, 100)
生成 1 到 100 的數值流
求 1 到 10 的數值總和: IntStream intStream = IntStream.rangeClosed(1, 10); int sum = intStream.sum();
NullPointerException 能夠說是每個 Java 程序員都很是討厭看到的一個詞,針對這個問題, Java 8 引入了一個新的容器類 Optional,能夠表明一個值存在或不存在,這樣就不用返回容易出問題的 null。以前文章的代碼中就常常出現這個類,也是針對這個問題進行的改進。
Optional 類比較經常使用的幾個方法有:
Optional 類還有三個特化版本 OptionalInt,OptionalLong,OptionalDouble,剛剛講到的數值流中的 max 方法返回的類型即是這個
Optional 類其中其實還有不少學問,講解它說不定也要開一篇文章,這裏先講那麼多,先知道基本怎麼用就能夠。
以前咱們獲得一個流是經過一個原始數據源轉換而來,其實咱們還能夠直接構建獲得流。
生成一個字符串流 Stream<String> stream = Stream.of("aaa", "bbb", "ccc");
根據參數的數組類型建立對應的流:
值得注意的是,還能夠規定只取數組的某部分,用到的是Arrays.stream(T[], int, int)
只取索引第 1 到第 2 位的: int[] a = {1, 2, 3, 4}; Arrays.stream(a, 1, 3).forEach(System.out :: println); 打印 2 ,3
Stream<String> stream = Files.lines(Paths.get("data.txt"));
每一個元素是給定文件的其中一行
兩個方法:
Stream.iterate(0, n -> n + 2) 生成流,首元素爲 0,以後依次加 2 Stream.generate(Math :: random) 生成流,爲 0 到 1 的隨機雙精度數 Stream.generate(() -> 1) 生成流,元素全爲 1
coollect 方法做爲終端操做,接受的是一個 Collector 接口參數,能對數據進行一些收集歸總操做
最經常使用的方法,把流中全部元素收集到一個 List, Set 或 Collection 中
List newlist = list.stream.collect(toList());
用於計算總和:
long l = list.stream().collect(counting());
沒錯,你應該想到了,下面這樣也能夠:
long l = list.stream().count();
推薦第二種
summing,沒錯,也是計算總和,不過這裏須要一個函數參數
計算 Person 年齡總和:
int sum = list.stream().collect(summingInt(Person::getAge));
固然,這個能夠也簡化爲:
int sum = list.stream().mapToInt(Person::getAge).sum();
除了上面兩種,其實還能夠:
int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();
推薦第二種
因而可知,函數式編程一般提供了多種方式來完成同一種操做
看名字就知道,求平均數
Double average = list.stream().collect(averagingInt(Person::getAge));
固然也能夠這樣寫
OptionalDouble average = list.stream().mapToInt(Person::getAge).average();
不過要注意的是,這兩種返回的值是不一樣類型的
這三個方法比較特殊,好比 summarizingInt 會返回 IntSummaryStatistics 類型
IntSummaryStatistics l = list.stream().collect(summarizingInt(Person::getAge));
IntSummaryStatistics 包含了計算出來的平均值,總數,總和,最值,能夠經過下面這些方法得到相應的數據
maxBy,minBy 兩個方法,須要一個 Comparator 接口做爲參數
Optional<Person> optional = list.stream().collect(maxBy(comparing(Person::getAge)));
咱們也能夠直接使用 max 方法得到一樣的結果
Optional<Person> optional = list.stream().max(comparing(Person::getAge));
也是一個比較經常使用的方法,對流裏面的字符串元素進行鏈接,其底層實現用的是專門用於字符串鏈接的 StringBuilder
String s = list.stream().map(Person::getName).collect(joining()); 結果:jackmiketom
String s = list.stream().map(Person::getName).collect(joining(",")); 結果:jack,mike,tom
joining 還有一個比較特別的重載方法:
String s = list.stream().map(Person::getName).collect(joining(" and ", "Today ", " play games.")); 結果:Today jack and mike and tom play games.
即 Today 放開頭,play games. 放結尾,and 在中間鏈接各個字符串
groupingBy 用於將數據分組,最終返回一個 Map 類型
Map<Integer, List<Person>> map = list.stream().collect(groupingBy(Person::getAge));
例子中咱們按照年齡 age 分組,每個 Person 對象中年齡相同的歸爲一組
另外能夠看出,Person::getAge
決定 Map 的鍵(Integer 類型),list 類型決定 Map 的值(List
groupingBy 能夠接受一個第二參數實現多級分組:
Map<Integer, Map<T, List<Person>>> map = list.stream().collect(groupingBy(Person::getAge, groupBy(...)));
其中返回的 Map 鍵爲 Integer 類型,值爲 Map<T, List
Map<Integer, Integer> map = list.stream().collect(groupingBy(Person::getAge, summingInt(Person::getAge)));
該例子中,咱們經過年齡進行分組,而後 summingInt(Person::getAge))
分別計算每一組的年齡總和(Integer),最終返回一個 Map<Integer, Integer>
根據這個方法,咱們能夠知道,前面咱們寫的:
groupingBy(Person::getAge)
其實等同於:
groupingBy(Person::getAge, toList())
分區與分組的區別在於,分區是按照 true 和 false 來分的,所以partitioningBy 接受的參數的 lambda 也是 T -> boolean
根據年齡是否小於等於20來分區 Map<Boolean, List<Person>> map = list.stream() .collect(partitioningBy(p -> p.getAge() <= 20)); 打印輸出 { false=[Person{name='mike', age=25}, Person{name='tom', age=30}], true=[Person{name='jack', age=20}] }
一樣地 partitioningBy 也能夠添加一個收集器做爲第二參數,進行相似 groupBy 的多重分區等等操做。
以前我就講到了 parallelStream 方法能生成並行流,所以你一般可使用 parallelStream 來代替 stream 方法,可是並行的性能問題很是值得咱們思考
比方說下面這個例子
int i = Stream.iterate(1, a -> a + 1).limit(100).parallel().reduce(0, Integer::sum);
咱們經過這樣一行代碼來計算 1 到 100 的全部數的和,咱們使用了 parallel 來實現並行。
但其實是,這樣的計算,效率是很是低的,比不使用並行還低!一方面是由於裝箱問題,這個前面也提到過,就再也不贅述,還有一方面就是 iterate 方法很難把這些數分紅多個獨立塊來並行執行,所以無形之中下降了效率。
這就說到流的可分解性問題了,使用並行的時候,咱們要注意流背後的數據結構是否易於分解。好比衆所周知的 ArrayList 和 LinkedList,明顯前者在分解方面佔優。
咱們來看看一些數據源的可分解性狀況
數據源 | 可分解性 |
---|---|
ArrayList | 極佳 |
LinkedList | 差 |
IntStream.range | 極佳 |
Stream.iterate | 差 |
HashSet | 好 |
TreeSet | 好 |
除了可分解性,和剛剛提到的裝箱問題,還有一點值得注意的是一些操做自己在並行流上的性能就比順序流要差,好比:limit,findFirst,由於這兩個方法會考慮元素的順序性,而並行自己就是違背順序性的,也是由於如此 findAny 通常比 findFirst 的效率要高。