简洁又快速地处理集合——Java8 Stream(下)
上一篇文章我講解 Stream 流的基本原理,以及它與集合的區(qū)別關系,講了那么多抽象的,本篇文章我們開始實戰(zhàn),講解流的各個方法以及各種操作
沒有看過上篇文章的可以先點擊進去學習一下 簡潔又快速地處理集合——Java8 Stream(上),當然你直接看這篇也可以,不過了解其本身才能更融會貫通哦。
值得注意的是:學習 Stream 之前必須先學習 lambda 的相關知識。本文也假設讀者已經(jīng)掌握 lambda 的相關知識。
本篇文章主要內(nèi)容:
- 流基本的常用方法
- 一種特化形式的流——數(shù)值流
- Optional 類
- 如何構建一個流
- collect 方法
- 并行流相關問題
一. 一般方法
首先我們先創(chuàng)建一個 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;1. stream() / parallelStream()
最常用到的方法,將集合轉(zhuǎn)換為流
List list = new ArrayList(); // return Stream<E> list.stream();而 parallelStream() 是并行流方法,能夠讓數(shù)據(jù)集執(zhí)行并行操作,后面會更詳細地講解
2. filter(T -> boolean)
保留 boolean 為 true 的元素
保留年齡為 20 的 person 元素 list = list.stream().filter(person -> person.getAge() == 20).collect(toList());打印輸出 [Person{name='jack', age=20}]collect(toList()) 可以把流轉(zhuǎn)換為 List 類型,這個以后會講解
3. distinct()
去除重復元素,這個方法是通過類的 equals 方法來判斷兩個元素是否相等的
如例子中的 Person 類,需要先定義好 equals 方法,不然類似[Person{name='jack', age=20}, Person{name='jack', age=20}] 這樣的情況是不會處理的
4. sorted() / sorted((T, T) -> int)
如果流中的元素的類實現(xiàn)了 Comparable 接口,即有自己的排序規(guī)則,那么可以直接調(diào)用 sorted() 方法對元素進行排序,如 Stream
反之, 需要調(diào)用 sorted((T, T) -> int) 實現(xiàn) Comparator 接口
根據(jù)年齡大小來比較: list = list.stream().sorted((p1, p2) -> p1.getAge() - p2.getAge()).collect(toList());當然這個可以簡化為
list = list.stream().sorted(Comparator.comparingInt(Person::getAge)).collect(toList());5. limit(long n)
返回前 n 個元素
list = list.stream().limit(2).collect(toList());打印輸出 [Person{name='jack', age=20}, Person{name='mike', age=25}]6. skip(long n)
去除前 n 個元素
list = list.stream().skip(2).collect(toList());打印輸出 [Person{name='tom', age=30}]tips:
- 用在 limit(n) 前面時,先去除前 m 個元素再返回剩余元素的前 n 個元素
- limit(n) 用在 skip(m) 前面時,先返回前 n 個元素再在剩余的 n 個元素中去除 m 個元素
7. map(T -> R)
將流中的每一個元素 T 映射為 R(類似類型轉(zhuǎn)換)
List<String> newlist = list.stream().map(Person::getName).collect(toList());newlist 里面的元素為 list 中每一個 Person 對象的 name 變量
8. flatMap(T -> Stream)
將流中的每一個元素 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 流,然后 flatMap 會將每一個流連接成為一個流,最終返回我們需要的 Stream
9. anyMatch(T -> boolean)
流中是否有一個元素匹配給定的 T -> boolean 條件
是否存在一個 person 對象的 age 等于 20: boolean b = list.stream().anyMatch(person -> person.getAge() == 20);10. allMatch(T -> boolean)
流中是否所有元素都匹配給定的 T -> boolean 條件
11. noneMatch(T -> boolean)
流中是否沒有元素匹配給定的 T -> boolean 條件
12. findAny() 和 findFirst()
- findAny():找到其中一個元素 (使用 stream() 時找到的是第一個元素;使用 parallelStream() 并行時找到的是其中一個元素)
- findFirst():找到第一個元素
值得注意的是,這兩個方法返回的是一個 Optional 對象,它是一個容器類,能代表一個值存在或不存在,這個后面會講到
13. reduce((T, T) -> T) 和 reduce(T, (T, T) -> T)
用于組合流中的元素,如求和,求積,求最大值等
計算年齡總和: 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 第一個參數(shù) 0 代表起始值為 0,lambda (a, b) -> a + b 即將兩值相加產(chǎn)生一個新值
同樣地:
計算年齡總乘積: 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 類型
13. count()
返回流中元素個數(shù),結果為 long 類型
14. collect()
收集方法,我們很常用的是 collect(toList()),當然還有 collect(toSet()) 等,參數(shù)是一個收集器接口,這個后面會另外講
15. forEach()
返回結果為 void,很明顯我們可以通過它來干什么了,比方說:
### 16. unordered() 還有這個比較不起眼的方法,返回一個等效的無序流,當然如果流本身就是無序的話,那可能就會直接返回其本身打印各個元素: list.stream().forEach(System.out::println);再比如說 MyBatis 里面訪問數(shù)據(jù)庫的 mapper 方法:
向數(shù)據(jù)庫插入新元素: list.stream().forEach(PersonMapper::insertPerson);二. 數(shù)值流
前面介紹的如
int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum); 計算元素總和的方法其中暗含了裝箱成本,map(Person::getAge) 方法過后流變成了 Stream 類型,而每個 Integer 都要拆箱成一個原始類型再進行 sum 方法求和,這樣大大影響了效率。
針對這個問題 Java 8 有良心地引入了數(shù)值流 IntStream, DoubleStream, LongStream,這種流中的元素都是原始數(shù)據(jù)類型,分別是 int,double,long
1. 流與數(shù)值流的轉(zhuǎn)換
流轉(zhuǎn)換為數(shù)值流
- mapToInt(T -> int) : return IntStream
- mapToDouble(T -> double) : return DoubleStream
- mapToLong(T -> long) : return LongStream
當然如果是下面這樣便會出錯
LongStream longStream = list.stream().mapToInt(Person::getAge);因為 getAge 方法返回的是 int 類型(返回的如果是 Integer,一樣可以轉(zhuǎn)換為 IntStream)
數(shù)值流轉(zhuǎn)換為流
很簡單,就一個 boxed
Stream<Integer> stream = intStream.boxed();2. 數(shù)值流方法
下面這些方法作用不用多說,看名字就知道:
- sum()
- max()
- min()
- average() 等...
3. 數(shù)值范圍
IntStream 與 LongStream 擁有 range 和 rangeClosed 方法用于數(shù)值范圍處理
- IntStream : rangeClosed(int, int) / range(int, int)
- LongStream : rangeClosed(long, long) / range(long, long)
這兩個方法的區(qū)別在于一個是閉區(qū)間,一個是半開半閉區(qū)間:
- rangeClosed(1, 100) :[1, 100]
- range(1, 100) :[1, 100)
我們可以利用 IntStream.rangeClosed(1, 100) 生成 1 到 100 的數(shù)值流
求 1 到 10 的數(shù)值總和: IntStream intStream = IntStream.rangeClosed(1, 10); int sum = intStream.sum();三. Optional 類
NullPointerException 可以說是每一個 Java 程序員都非常討厭看到的一個詞,針對這個問題, Java 8 引入了一個新的容器類 Optional,可以代表一個值存在或不存在,這樣就不用返回容易出問題的 null。之前文章的代碼中就經(jīng)常出現(xiàn)這個類,也是針對這個問題進行的改進。
Optional 類比較常用的幾個方法有:
- isPresent() :值存在時返回 true,反之 flase
- get() :返回當前值,若值不存在會拋出異常
- orElse(T) :值存在時返回該值,否則返回 T 的值
Optional 類還有三個特化版本 OptionalInt,OptionalLong,OptionalDouble,剛剛講到的數(shù)值流中的 max 方法返回的類型便是這個
Optional 類其中其實還有很多學問,講解它說不定也要開一篇文章,這里先講那么多,先知道基本怎么用就可以。
四. 構建流
之前我們得到一個流是通過一個原始數(shù)據(jù)源轉(zhuǎn)換而來,其實我們還可以直接構建得到流。
1. 值創(chuàng)建流
- Stream.of(T...) : Stream.of("aa", "bb") 生成流
- Stream.empty() : 生成空流
2. 數(shù)組創(chuàng)建流
根據(jù)參數(shù)的數(shù)組類型創(chuàng)建對應的流:
- Arrays.stream(T[ ])
- Arrays.stream(int[ ])
- Arrays.stream(double[ ])
- Arrays.stream(long[ ])
值得注意的是,還可以規(guī)定只取數(shù)組的某部分,用到的是Arrays.stream(T[], int, int)
只取索引第 1 到第 2 位的: int[] a = {1, 2, 3, 4}; Arrays.stream(a, 1, 3).forEach(System.out :: println);打印 2 ,33. 文件生成流
Stream<String> stream = Files.lines(Paths.get("data.txt"));每個元素是給定文件的其中一行
4. 函數(shù)生成流
兩個方法:
- iterate : 依次對每個新生成的值應用函數(shù)
- generate :接受一個函數(shù),生成一個新的值
五. collect 收集數(shù)據(jù)
coollect 方法作為終端操作,接受的是一個 Collector 接口參數(shù),能對數(shù)據(jù)進行一些收集歸總操作
1. 收集
最常用的方法,把流中所有元素收集到一個 List, Set 或 Collection 中
- toList
- toSet
- toCollection
2. 匯總
(1)counting
用于計算總和:
long l = list.stream().collect(counting());沒錯,你應該想到了,下面這樣也可以:
long l = list.stream().count();推薦第二種
(2)summingInt ,summingLong ,summingDouble
summing,沒錯,也是計算總和,不過這里需要一個函數(shù)參數(shù)
計算 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();推薦第二種
由此可見,函數(shù)式編程通常提供了多種方式來完成同一種操作
(3)averagingInt,averagingLong,averagingDouble
看名字就知道,求平均數(shù)
Double average = list.stream().collect(averagingInt(Person::getAge));當然也可以這樣寫
OptionalDouble average = list.stream().mapToInt(Person::getAge).average();不過要注意的是,這兩種返回的值是不同類型的
(4)summarizingInt,summarizingLong,summarizingDouble
這三個方法比較特殊,比如 summarizingInt 會返回 IntSummaryStatistics 類型
IntSummaryStatistics l = list.stream().collect(summarizingInt(Person::getAge));IntSummaryStatistics 包含了計算出來的平均值,總數(shù),總和,最值,可以通過下面這些方法獲得相應的數(shù)據(jù)
3. 取最值
maxBy,minBy 兩個方法,需要一個 Comparator 接口作為參數(shù)
Optional<Person> optional = list.stream().collect(maxBy(comparing(Person::getAge)));我們也可以直接使用 max 方法獲得同樣的結果
Optional<Person> optional = list.stream().max(comparing(Person::getAge));4. joining 連接字符串
也是一個比較常用的方法,對流里面的字符串元素進行連接,其底層實現(xiàn)用的是專門用于字符串連接的 StringBuilder
String s = list.stream().map(Person::getName).collect(joining());結果:jackmiketom String s = list.stream().map(Person::getName).collect(joining(","));結果:jack,mike,tomjoining 還有一個比較特別的重載方法:
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 在中間連接各個字符串
5. groupingBy 分組
groupingBy 用于將數(shù)據(jù)分組,最終返回一個 Map 類型
Map<Integer, List<Person>> map = list.stream().collect(groupingBy(Person::getAge));例子中我們按照年齡 age 分組,每一個 Person 對象中年齡相同的歸為一組
另外可以看出,Person::getAge 決定 Map 的鍵(Integer 類型),list 類型決定 Map 的值(List 類型)
多級分組
groupingBy 可以接受一個第二參數(shù)實現(xiàn)多級分組:
Map<Integer, Map<T, List<Person>>> map = list.stream().collect(groupingBy(Person::getAge, groupBy(...)));其中返回的 Map 鍵為 Integer 類型,值為 Map<T, List> 類型,即參數(shù)中 groupBy(...) 返回的類型
按組收集數(shù)據(jù)
Map<Integer, Integer> map = list.stream().collect(groupingBy(Person::getAge, summingInt(Person::getAge)));該例子中,我們通過年齡進行分組,然后 summingInt(Person::getAge)) 分別計算每一組的年齡總和(Integer),最終返回一個 Map<Integer, Integer>
根據(jù)這個方法,我們可以知道,前面我們寫的:
groupingBy(Person::getAge)其實等同于:
groupingBy(Person::getAge, toList())6. partitioningBy 分區(qū)
分區(qū)與分組的區(qū)別在于,分區(qū)是按照 true 和 false 來分的,因此partitioningBy 接受的參數(shù)的 lambda 也是 T -> boolean
根據(jù)年齡是否小于等于20來分區(qū) 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 也可以添加一個收集器作為第二參數(shù),進行類似 groupBy 的多重分區(qū)等等操作。
六. 并行
之前我就講到了 parallelStream 方法能生成并行流,因此你通常可以使用 parallelStream 來代替 stream 方法,但是并行的性能問題非常值得我們思考
比方說下面這個例子
int i = Stream.iterate(1, a -> a + 1).limit(100).parallel().reduce(0, Integer::sum);我們通過這樣一行代碼來計算 1 到 100 的所有數(shù)的和,我們使用了 parallel 來實現(xiàn)并行。
但實際上是,這樣的計算,效率是非常低的,比不使用并行還低!一方面是因為裝箱問題,這個前面也提到過,就不再贅述,還有一方面就是 iterate 方法很難把這些數(shù)分成多個獨立塊來并行執(zhí)行,因此無形之中降低了效率。
流的可分解性
這就說到流的可分解性問題了,使用并行的時候,我們要注意流背后的數(shù)據(jù)結構是否易于分解。比如眾所周知的 ArrayList 和 LinkedList,明顯前者在分解方面占優(yōu)。
我們來看看一些數(shù)據(jù)源的可分解性情況
| ArrayList | 極佳 |
| LinkedList | 差 |
| IntStream.range | 極佳 |
| Stream.iterate | 差 |
| HashSet | 好 |
| TreeSet | 好 |
順序性
除了可分解性,和剛剛提到的裝箱問題,還有一點值得注意的是一些操作本身在并行流上的性能就比順序流要差,比如:limit,findFirst,因為這兩個方法會考慮元素的順序性,而并行本身就是違背順序性的,也是因為如此 findAny 一般比 findFirst 的效率要高。
相關閱讀
- 簡潔又快速地處理集合——Java8 Stream(上)
猜你喜歡
- 你必須搞清楚的String,StringBuilder,StringBuffer
- 分享一些 Java 后端的個人干貨
- 教你 Shiro + SpringBoot 整合 JWT
- 教你 Shiro 整合 SpringBoot,避開各種坑
你的關注就是我不斷發(fā)文最大的動力
轉(zhuǎn)載于:https://www.cnblogs.com/HowieYuan/p/9394552.html
總結
以上是生活随笔為你收集整理的简洁又快速地处理集合——Java8 Stream(下)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【数据库学习笔记】——创建数据库文件
- 下一篇: Java 支付宝支付,退款,单笔转账到支