Java中快速处理集合_简洁又快速地处理集合——Java8 Stream(上)
作者:Howie_Y,系原創投稿
主頁:www.jianshu.com/u/79638e5f0743
Java 8 發布至今也已經好幾年過去,如今 Java 也已經向 11 邁去,但是 Java 8 作出的改變可以說是革命性的,影響足夠深遠,學習 Java 8 應該是 Java 開發者的必修課。
今天給大家帶來 Java 8 Stream 講解,為什么直接講這個,是因為只要你學完,立刻就能上手,并能讓它在你的代碼中大展身手。
值得注意的是:學習 Stream 之前必須先學習 lambda 的相關知識。本文也假設讀者已經掌握 lambda 的相關知識。
本篇文章主要內容:
介紹 Stream 以及 Stream 是如何處理集合的
介紹 Stream 與集合的關系與區別
Stream 的基本方法介紹
一. 什么是 Stream
Stream 中文稱為 “流”,通過將集合轉換為這么一種叫做 “流” 的元素序列,通過聲明性方式,能夠對集合中的每個元素進行一系列并行或串行的流水線操作。
換句話說,你只需要告訴流你的要求,流便會在背后自行根據要求對元素進行處理,而你只需要 “坐享其成”。
二. 流操作
整個流操作就是一條流水線,將元素放在流水線上一個個地進行處理。
其中數據源便是原始集合,然后將如 List
的集合轉換為 Stream
類型的流,并對流進行一系列的中間操作,比如過濾保留部分元素、對元素進行排序、類型轉換等;最后再進行一個終端操作,可以把 Stream 轉換回集合類型,也可以直接對其中的各個元素進行處理,比如打印、比如計算總數、計算最大值等等
很重要的一點是,很多流操作本身就會返回一個流,所以多個操作可以直接連接起來,我們來看看一條 Stream 操作的代碼:
如果是以前,進行這么一系列操作,你需要做個迭代器或者 foreach 循環,然后遍歷,一步步地親力親為地去完成這些操作;但是如果使用流,你便可以直接聲明式地下指令,流會幫你完成這些操作。
有沒有想到什么類似的?是的,就像 SQL 語句一樣, select username from user where id = 1,你只要說明:“我需要 id 是 1 (id = 1)的用戶(user)的用戶名(username )”,那么就可以得到自己想要的數據,而不需要自己親自去數據庫里面循環遍歷查找。
三. 流與集合
什么時候計算
Stream 和集合的其中一個差異在于什么時候進行計算。
一個集合,它會包含當前數據結構中所有的值,你可以隨時增刪,但是集合里面的元素毫無疑問地都是已經計算好了的。
流則是按需計算,按照使用者的需要計算數據,你可以想象我們通過搜索引擎進行搜索,搜索出來的條目并不是全部呈現出來的,而且先顯示最符合的前 10 條或者前 20 條,只有在點擊 “下一頁” 的時候,才會再輸出新的 10 條。
再比方在線觀看電影和你硬盤里面的電影,也是差不多的道理。
外部迭代和內部迭代
Stream 和集合的另一個差異在于迭代。
我們可以把集合比作一個工廠的倉庫,一開始工廠比較落后,要對貨物作什么修改,只能工人親自走進倉庫對貨物進行處理,有時候還要將處理后的貨物放到一個新的倉庫里面。在這個時期,我們需要親自去做迭代,一個個地找到需要的貨物,并進行處理,這叫做外部迭代。
后來工廠發展了起來,配備了流水線作業,只要根據需求設計出相應的流水線,然后工人只要把貨物放到流水線上,就可以等著接收成果了,而且流水線還可以根據要求直接把貨物輸送到相應的倉庫。
這就叫做內部迭代,流水線已經幫你把迭代給完成了,你只需要說要干什么就可以了(即設計出合理的流水線)。
Java 8 引入 Stream 很大程度是因為,流的內部迭代可以自動選擇一種合適你硬件的數據表示和并行實現;而以往程序員自己進行 foreach 之類的時候,則需要自己去管理并行等問題。
一次性的流
流和迭代器類似,只能迭代一次。
Stream stream = list.stream().map(Person::getName).sorted().limit(10);
List newList = stream.collect(toList());
List newList2 = stream.collect(toList());
上面代碼中第三行會報錯,因為第二行已經使用過這個流,這個流已經被消費掉了
四. 方法介紹,開始實戰
首先我們先創建一個 Person 泛型的 List
List 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()
最常用到的方法,將集合轉換為流
List list = new ArrayList();
// return Stream
list.stream();
而 parallelStream() 是并行流方法,能夠讓數據集執行并行操作,后面會更詳細地講解
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()) 可以把流轉換為 List 類型,這個以后會講解
3. distinct()
去除重復元素,這個方法是通過類的 equals 方法來判斷兩個元素是否相等的
如例子中的 Person 類,需要先定義好 equals 方法,不然類似[Person{name='jack', age=20}, Person{name='jack', age=20}] 這樣的情況是不會處理的
4. sorted() / sorted((T, T) -> int)
如果流中的元素的類實現了 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());
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 個元素
list = list.stream()
.limit(2)
.skip(1)
.collect(toList());
打印輸出 [Person{name='mike', age=25}]
7. map(T -> R)
將流中的每一個元素 T 映射為 R(類似類型轉換)
List newlist =
list.stream().map(Person::getName).collect(toList());
newlist 里面的元素為 list 中每一個 Person 對象的 name 變量
8. flatMap(T -> Stream
)
將流中的每一個元素 T 映射為一個流,再把每一個流連接成為一個流
List 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
,因為 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 第一個參數 0 代表起始值為 0,lambda (a, b) -> a + b 即將兩值相加產生一個新值。
同樣地:
計算年齡總乘積:
int sum = list.stream().map(Person::getAge).reduce(1, (a, b) -> a * b);
當然也可以
Optional sum = list.stream().map(Person::getAge).reduce(Integer::sum);
即不接受任何起始值,但因為沒有初始值,需要考慮結果可能不存在的情況,因此返回的是 Optional 類型。
13. count()
返回流中元素個數,結果為 long 類型
14. collect()
收集方法,我們很常用的是 collect(toList()),當然還有 collect(toSet()) 等,參數是一個收集器接口,這個后面會另外講。
15. forEach()
返回結果為 void,很明顯我們可以通過它來干什么了,比方說:
### 16. unordered()還有這個比較不起眼的方法,
#返回一個等效的無序流,當然如果流本身就是無序的話,那可能就會直接返回其本身
打印各個元素:
list.stream().forEach(System.out::println);
再比如說 MyBatis 里面訪問數據庫的 mapper 方法:
向數據庫插入新元素:
list.stream().forEach(PersonMapper::insertPerson);
推薦大而全的【后端技術精選】
總結
以上是生活随笔為你收集整理的Java中快速处理集合_简洁又快速地处理集合——Java8 Stream(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RX 7000显卡稳了!AMD公布5nm
- 下一篇: java方法有excel实现_Java实