一. 什么是 Stream
Stream 中文稱為 “流”,是Java8新特性主要是用來處理集合數據的,可以將其看作一個高級迭代器,通過將集合轉換為這么一種叫做 “流” 的元素序列,通過聲明性方式,能夠對集合中的每個元素進行一系列並行或串行的流水線操作。
換句話說,你只需要告訴流你的要求,流便會在背后自行根據要求對元素進行處理,而你只需要 “坐享其成”。
二. 流操作
整個流操作就是一條流水線,將元素放在流水線上一個個地進行處理。
其中數據源便是原始集合,然后將如 List<T> 的集合轉換為 Stream<T> 類型的流,並對流進行一系列的中間操作,比如過濾保留部分元素、對元素進行排序、類型轉換等;最后再進行一個終端操作,可以把 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<String> stream = list.stream().map(Person::getName).sorted().limit(10); List<String> newList = stream.collect(Collectors.toList()); List<String> newList2 = stream.collect(Collectors.toList());
上面代碼中第三行會報錯,因為第二行已經使用過這個流,這個流已經被消費掉了
四. 方法介紹
一. 一般方法
值得注意的是:學習 Stream 之前必須先學習 lambda 的相關知識。
首先我們先定義一個類 Person 並創建一個 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()
最常用到的方法,將集合轉換為流
List list = new ArrayList(); // return Stream<E> list.stream();
而 parallelStream() 是並行流方法,能夠讓數據集執行並行操作,后面會更詳細地講解
2. filter(T -> boolean)
保留 boolean 為 true 的元素
//保留年齡為 20 的 person 元素 list = list.stream() .filter(person -> person.getAge() == 20) .collect(Collectors.toList());
打印輸出 [Person{name='jack', age=20}]
//把list里滿足某個條件的成員輸出到新的list
//輸出用戶名等於張三的
List<User2> lstUsers = lstUsersAll.stream().filter(t -> "張三".equals(t.getName())).collect(Collectors.toList());
collect(Collectors.toList()) 可以把流轉換為 List 類型
long count = list.stream().filter(t -> "張三".equals(t.getName())).count(); if ((int) count == 0) { }
3. distinct()
去除重復元素,這個方法是通過類的 equals 方法來判斷兩個元素是否相等的
如例子中的 Person 類,需要先定義好 equals 方法,不然類似[Person{name='jack', age=20}, Person{name='jack', age=20}] 這樣的情況是不會處理的
List uniqueList = list.stream().distinct().collect(Collectors.toList());
對象去重
List<User> userList = new ArrayList<User>(); userList.add(new User("小黃",10)); userList.add(new User("小紅",23)); userList.add(new User("小黃",78)); userList.add(new User("小黃",10)); //根據name屬性去重 List<User> unique1 = userList.stream().collect(
Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new)); System.out.println(unique1.toString()); //根據name,age屬性去重 List<User> unique2 = userList.stream().collect( Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o ->
{
return o.getName() + "," + o.getAge(); }))), ArrayList::new)); System.out.println(unique2.toString());
輸出如下:

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(Collectors.toList());
當然這個可以簡化為
list = list.stream() .sorted(Comparator.comparingInt(Person::getAge)) .collect(Collectors.toList());
//升序(默認)
list = list.stream()
.sorted(Comparator.comparing(Person::getAge))
.collect(Collectors.toList());
//降序
list = list.stream()
.sorted(Comparator.comparing(Person::getAge).reversed())
.collect(Collectors.toList());
注意:日期類型的排序需小心,因為如果日期為空,使用上面一般的方式排序會報錯的。所以最好使用下面方式二排序
//方式一:排序(按【創建時間】升序排列) //如果創建時間有為空的就會報錯 list = list.stream().sorted(Comparator.comparing(Person::getCreatetime))
.collect(Collectors.toList()); //方式二:排序(按【創建時間】升序排列) //nullsLast方法表示如果創建時間為空就排列到最后面
list = list.stream().sorted(Comparator.comparing(t -> t.getCreatetime(), Comparator.nullsLast(Date::compareTo)))
.collect(Collectors.toList());
//nullsFirst方法表示如果創建時間為空就排列到最前面
list = list.stream().sorted(Comparator.comparing(t -> t.getCreatetime(), Comparator.nullsFirst(Date::compareTo)))
.collect(Collectors.toList());
涉及到多字段組合排序時使用 thenComparing 方法
//今日待跟蹤(多字段組合排序) list = list.stream().sorted(Comparator.comparing(Person::getAge).thenComparing(Comparator.comparing(Person::getCreatetime, Comparator.nullsLast(Date::compareTo))))
.collect(Collectors.toList());
實現中文漢字按拼音排序
要實現漢字按首字母排序,主要是設置語言環境,如下語句設置語言環境:
這里用到了Collator類,此類實現了Comparator接口,用他的getInstance就可以用指定的語言環境來構造一個Collator對象:

然后用如下語句創建Comparator:
Comparator<Object> com = Collator.getInstance(java.util.Locale.CHINA);
//獲取中文環境 Comparator<Object> com = Collator.getInstance(java.util.Locale.CHINA); String[] newArray={"中山","汕頭","廣州","安慶","陽江","南京","武漢","北京","安陽","北方"}; List<String> list = Arrays.asList(newArray); Collections.sort(list, com); for(String i:list){ System.out.print(i+" "); } 輸出結果是: 安慶 安陽 北方 北京 廣州 南京 汕頭 武漢 陽江 中山
實現List集合中對象元素按其屬性的中文拼音排序,這里重寫compare方法
//獲取商戶list集合 List<Merch> merchList = xxxxService.queryMerchList(); //Collections工具類的sort()方法對list集合元素排序 Collections.sort(merchList, new Comparator<Merch>() { @Override public int compare(Merch info1, Merch info2) { //獲取中文環境 Comparator<Object> com = Collator.getInstance(java.util.Locale.CHINA); //按照漢語拼音排序 return com.compare(info1.getMerchName(), info2.getMerchName()); }
}.thenComparing(Comparator.comparing(Person::getCreatetime, Comparator.nullsLast(Date::compareTo))));
或者用下面這兩種寫法也可以實現
//Collator類是用來執行區分語言環境的String比較的,這里是選擇中文環境 Comparator<Object> china_compare = Collator.getInstance(java.util.Locale.CHINA); //方式一 Collections.sort(list, Comparator.comparing(Person::getNamefirst, china_compare) .thenComparing(Comparator.comparing(Person::getCreatetime, Comparator.nullsLast(Date::compareTo)))); //方式二 list = list.stream().sorted(Comparator.comparing(Person::getNamefirst, china_compare) .thenComparing(Comparator.comparing(Person::getCreatetime, Comparator.nullsLast(Date::compareTo)))).collect(Collectors.toList());
5. limit(long n)
返回前 n 個元素
list = list.stream() .limit(2) .collect(Collectors.toList()); 打印輸出 [Person{name='jack', age=20}, Person{name='mike', age=25}]
6. skip(long n)
去除前 n 個元素
list = list.stream() .skip(2) .collect(Collectors.toList()); 打印輸出 [Person{name='tom', age=30}]
說明:
- 用在 limit(n) 前面時,先去除前 m 個元素再返回剩余元素的前 n 個元素
- limit(n) 用在 skip(m) 前面時,先返回前 n 個元素再在剩余的 n 個元素中去除 m 個元素
list = list.stream() .limit(2) .skip(1) .collect(Collectors.toList()); 打印輸出 [Person{name='mike', age=25}]
7. map(T -> R)
將流中的每一個元素 T 映射為 R(類似類型轉換)
- map用來歸類,結果一般是一組數據,比如可以將list中的學生姓名映射到一個新的stream中。
//使用map方法獲取list數據中的name List<String> newlist = list.stream().map(Student::getName).collect(Collectors.toList());
//使用map方法獲取list數據中的name的長度 List<Integer> length = list.stream().map(Student::getName).map(String::length).collect(Collectors.toList()); //將每人的分數-10 List<Integer> score = list.stream().map(Student::getScore).map(i -> i - 10).collect(Collectors.toList());
newlist 里面的元素為 list 中每一個 Student 對象的 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(Collectors.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)
用於組合流中的元素,如求和,求積,求最大值等
- reduce用來計算值,結果是一個值,比如計算最高分。
計算年齡總和: 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);
當然也可以
Integer total2 = listbo.stream().map(Student::getScore).reduce(Integer::sum).get(); Optional<Integer> sum = list.stream().map(Student::getScore).reduce(Integer::sum);
計算學生總分,返回Optional類型的數據,該類型是java8中新增的,主要用來避免空指針異常 Optional<Integer> totalScore2 = list.stream().map(Student::getScore).reduce((a,b) -> a + b); 計算最高分和最低分 Optional<Integer> max = list.stream().map(Student::getScore).reduce(Integer::max); Optional<Integer> min = list.stream().map(Student::getScore).reduce(Integer::min);
即不接受任何起始值,但因為沒有初始值,需要考慮結果可能不存在的情況,因此返回的是 Optional 類型
14. count()
返回流中元素個數,結果為 long 類型
15. collect()
收集方法,我們很常用的是 collect(Collectors.toList()),當然還有 collect(Collectors.toSet()) 等,參數是一個收集器接口,這個后面會另外講
16. forEach()
返回結果為 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 類型,而每個 Integer 都要拆箱成一個原始類型再進行 sum 方法求和,這樣大大影響了效率。
針對這個問題 Java 8 有良心地引入了數值流 IntStream, DoubleStream, LongStream,這種流中的元素都是原始數據類型,分別是 int,double,long
1. 流與數值流的轉換
流轉換為數值流
- mapToInt(T -> int) : return IntStream
- mapToDouble(T -> double) : return DoubleStream
- mapToLong(T -> long) : return LongStream
IntStream intStream = list.stream().mapToInt(Person::getAge);
當然如果是下面這樣便會出錯
LongStream longStream = list.stream().mapToInt(Person::getAge);
因為 getAge 方法返回的是 int 類型(返回的如果是 Integer,一樣可以轉換為 IntStream)
數值流轉換為流
很簡單,就一個 boxed
Stream<Integer> stream = intStream.boxed();
2. 數值流方法
下面這些方法作用不用多說,看名字就知道:
- sum()
- max()
- min()
- average() 等...
3. 數值范圍
IntStream 與 LongStream 擁有 range 和 rangeClosed 方法用於數值范圍處理
- IntStream : rangeClosed(int, int) / range(int, int)
- LongStream : rangeClosed(long, long) / range(long, long)
這兩個方法的區別在於一個是閉區間,一個是半開半閉區間:
- rangeClosed(1, 100) :[1, 100]
- range(1, 100) :[1, 100)
我們可以利用 IntStream.rangeClosed(1, 100) 生成 1 到 100 的數值流
求 1 到 10 的數值總和: IntStream intStream = IntStream.rangeClosed(1, 10); int sum = intStream.sum();
三. Optional 類
NullPointerException 可以說是每一個 Java 程序員都非常討厭看到的一個詞,針對這個問題, Java 8 引入了一個新的容器類 Optional,可以代表一個值存在或不存在,這樣就不用返回容易出問題的 null。
Optional 類比較常用的幾個方法有:
- isPresent() :值存在時返回 true,反之 flase
- get() :返回當前值,若值不存在會拋出異常
- orElse(T) :值存在時返回該值,否則返回 T 的值
Optional 類還有三個特化版本 OptionalInt,OptionalLong,OptionalDouble,剛剛講到的數值流中的 max 方法返回的類型便是這個
Optional 類其中其實還有很多學問,講解它說不定也要開一篇文章,這里先講那么多,先知道基本怎么用就可以。
https://www.cnblogs.com/shenlanzhizun/p/6025536.html
public class TestOptional { public static void main(String[] args) { List<Student> studentList = InitData.getStudent(); //計算分數在60分一下的分數總和 Optional<Integer> score = studentList.stream() .map(Student :: getScore) .filter(s -> s<60) .reduce((a,b) -> a+b); //沒有60分以下的,以前不加判斷就會出現空指針異常 System.out.println(score.orElse(0)); //為null則返回0 Map<Integer,String> map = new HashMap<>(); map.put(20180001,"章子"); map.put(20180002,"小米"); map.put(20180003,"大黃"); map.put(20180004,"靚妹"); String name = Optional.ofNullable(map.get(20180005)).orElse("無"); System.out.println(name); //無 } }
四. 構建流
之前我們得到一個流是通過一個原始數據源轉換而來,其實我們還可以直接構建得到流。
1. 值創建流
- Stream.of(T...) : Stream.of("aa", "bb") 生成流
生成一個字符串流 Stream<String> stream = Stream.of("aaa", "bbb", "ccc");
- Stream.empty() : 生成空流
2. 數組創建流
根據參數的數組類型創建對應的流:
- Arrays.stream(T[ ])
- Arrays.stream(int[ ])
- Arrays.stream(double[ ])
- Arrays.stream(long[ ])
值得注意的是,還可以規定只取數組的某部分,用到的是Arrays.stream(T[], int, int)
只取索引第 1 到第 2 位的: int[] a = {1, 2, 3, 4}; Arrays.stream(a, 1, 3).forEach(System.out :: println); 打印 2 ,3
3. 文件生成流
Stream<String> stream = Files.lines(Paths.get("data.txt"));
每個元素是給定文件的其中一行
4. 函數生成流
兩個方法:
- iterate : 依次對每個新生成的值應用函數
- generate :接受一個函數,生成一個新的值
Stream.iterate(0, n -> n + 2) 生成流,首元素為 0,之后依次加 2 Stream.generate(Math :: random) 生成流,為 0 到 1 的隨機雙精度數 Stream.generate(() -> 1) 生成流,元素全為 1
五. collect 收集數據
coollect 方法作為終端操作,它離不開Collectors工具類。其實上面的代碼已經涉及到了該方法,比如collect(Collectors.toList())轉換成List集合,能對數據進行一些收集歸總操作。
1. 收集
最常用的方法,把流中所有元素收集到一個 List, Set 或 Collection 中
- Collectors.toList 轉換成List集合
- Collectors.toSet 轉換成set集合
- Collectors.toCollection(TreeSet::new) 轉換成特定的set集合
- Collectors.toMap
(x -> x, x -> x + 1)轉換成map
List newlist = list.stream.collect(Collectors.toList());
TreeSet<Integer> collect2 = Stream.of(1, 3, 4).collect(Collectors.toCollection(TreeSet::new));
Map<Integer, Integer> collect1 = Stream.of(1, 3, 4).collect(Collectors.toMap(x -> x, x -> x + 1));
2. 匯總
(1)counting
用於計算總和:
long l = list.stream().collect(Collectors.counting());
沒錯,你應該想到了,下面這樣也可以:
long l = list.stream().count();
推薦第二種
(2)summingInt ,summingLong ,summingDouble
summing,沒錯,也是計算總和,不過這里需要一個函數參數
計算 Person 年齡總和:
int sum = list.stream().collect(Collectors.summingInt(Person::getAge));
當然,這個可以也簡化為:
int sum = list.stream().mapToInt(Person::getAge).sum();
除了上面兩種,其實還可以:
int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();
推薦第二種
由此可見,函數式編程通常提供了多種方式來完成同一種操作
(3)averagingInt,averagingLong,averagingDouble
看名字就知道,求平均數
Double average = list.stream().collect(Collectors.averagingInt(Person::getAge));
當然也可以這樣寫
OptionalDouble average = list.stream().mapToInt(Person::getAge).average();
不過要注意的是,這兩種返回的值是不同類型的
(4)summarizingInt,summarizingLong,summarizingDouble
這三個方法比較特殊,比如 summarizingInt 會返回 IntSummaryStatistics 類型
IntSummaryStatistics l = list.stream().collect(Collectors.summarizingInt(Person::getAge));
IntSummaryStatistics 包含了計算出來的平均值,總數,總和,最值,可以通過下面這些方法獲得相應的數據

3. 取最值
maxBy,minBy 兩個方法,需要一個 Comparator 接口作為參數
Optional<Person> optional = list.stream().collect(Collectors.maxBy(Comparator.comparing(Person::getAge)));
我們也可以直接使用 max 方法獲得同樣的結果
Optional<Person> optional = list.stream().max(Comparator.comparing(Person::getAge));
4. joining 連接字符串
也是一個比較常用的方法,對流里面的字符串元素進行連接,其底層實現用的是專門用於字符串連接的 StringBuilder
String s = list.stream().map(Person::getName).collect(Collectors.joining()); 結果:jackmiketom String s = list.stream().map(Person::getName).collect(Collectors.joining(",")); 結果:jack,mike,tom
joining 還有一個比較特別的重載方法:
String s = list.stream().map(Person::getName).collect(Collectors.joining(" and ", "Today ", " play games.")); 結果:Today jack and mike and tom play games.
即 Today 放開頭,play games. 放結尾,and 在中間連接各個字符串
5. groupingBy 分組
groupingBy 用於將數據分組,最終返回一個 Map 類型
Map<Integer, List<Person>> map = list.stream().collect(Collectors.groupingBy(Person::getAge));
例子中我們按照年齡 age 分組,每一個 Person 對象中年齡相同的歸為一組
另外可以看出,Person::getAge 決定 Map 的鍵(Integer 類型),list 類型決定 Map 的值(List 類型)
多級分組
groupingBy 可以接受一個第二參數實現多級分組:
Map<Integer, Map<T, List<Person>>> map = list.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.groupingBy(Person::getName)));
其中返回的 Map 鍵為 Integer 類型,值為 Map<T, List> 類型,即參數中 groupingBy(...) 返回的類型
在分組后,獲取每個分組的第一個元素
Map<Integer, Person> resultList = list.stream().collect(
Collectors.groupingBy(Person::getAge,
Collectors.collectingAndThen(Collectors.toList(), value -> value.get(0))));
需要借助Collectors.collectingAndThen方法,對組內的元素進行處理,這里是獲取第一個元素
在分組后,獲取每個分組最大元素
Map<String, HitRuleConfig> configMap = list.stream().collect( Collectors.groupingBy(Person::getAge, //先根據age分組 Collectors.collectingAndThen(Collectors.reducing((c1, c2) -> c1.getVersionSort() > c2.getVersionSort() ? c1 : c2), Optional::get)));
先按照某一字段分組,再按照另外字段排序取最大的那個
按組收集數據
Map<Integer, Integer> map = list.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.summingInt(Person::getAge)));
該例子中,我們通過年齡進行分組,然后 summingInt(Person::getAge)) 分別計算每一組的年齡總和(Integer),最終返回一個 Map<Integer, Integer>
根據這個方法,我們可以知道,前面我們寫的:
groupingBy(Person::getAge)
其實等同於:
groupingBy(Person::getAge, Collectors.toList())
6. partitioningBy 分區
把數據分成兩部分,分區與分組的區別在於,分區是按照 true 和 false 來分的,因此 partitioningBy 接受的參數的 lambda 也是 T -> boolean
根據年齡是否小於等於20來分區 Map<Boolean, List<Person>> map = list.stream() .collect(Collectors.partitioningBy(p -> p.getAge() <= 20)); 打印輸出 { false=[Person{name='mike', age=25}, Person{name='tom', age=30}], true=[Person{name='jack', age=20}] }
同樣地 partitioningBy 也可以添加一個收集器作為第二參數,進行類似 groupBy 的多重分區等等操作。
7. reducing
Collectors.reducing(0, x -> x + 1, (x, y) -> x + y)):在求累計值的時候,還可以對參數值進行改變,這里是都+1后再求和。跟reduce方法有點類似,但reduce方法沒有第二個參數。
System.out.println(Stream.of(1, 3, 4).collect(Collectors.reducing(0, x -> x + 1, (x, y) -> x + y)));
8. collectingAndThen
Collectors.collectingAndThen(Collectors.joining(","), x -> x + "d"):先執行collect操作后再執行第二個參數的表達式。這里是先拼接字符串,再在最后+ "d"。
String str= Stream.of("a", "b", "c").collect(Collectors.collectingAndThen(Collectors.joining(","), x -> x + "d"));
9. mapping
Collectors.mapping(...):跟map操作類似,只是參數有點區別。
System.out.println(Stream.of("a", "b", "c").collect(Collectors.mapping(x -> x.toUpperCase(), Collectors.joining(","))));
六. 並行
之前我就講到了 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 的效率要高。
自己測試用的例子如下:
//返回流中元素個數
long count = listbo.stream().count();
//分頁
List<userBO> newlist1 = listbo.stream().skip(2).limit(5).collect(Collectors.toList());
//排序(默認升序)
List<userBO> newlist2 = listbo.stream().sorted(Comparator.comparing(userBO::getId)).collect(Collectors.toList());
//排序(降序)
List<userBO> newlist21 = listbo.stream().sorted(Comparator.comparing(userBO::getId).reversed()).collect(Collectors.toList());
//多字段組合排序(日期為空排序處理)
List<userBO> newlist22 = listbo.stream().sorted(Comparator.comparing(userBO::getAge).thenComparing(Comparator.comparing(userBO::getCreatetime, Comparator.nullsLast(Date::compareTo)))).collect(Collectors.toList());
//取單個字段
List<Integer> newlist4 = listbo.stream().map(userBO::getId).collect(Collectors.toList());
List<String> newlist = listbo.stream().map(userBO::getName).sorted().skip(10).limit(5).collect(Collectors.toList());
//過濾並去重
List<userBO> newlist3 = listbo.stream().filter(a -> a.getName().equals("張三")).distinct().collect(Collectors.toList());
//過濾取第一個
Optional<userBO> firstA = listbo.stream().filter(a -> "張三".equals(a.getName())).findFirst();
if(firstA.isPresent()){
userBO A = firstA.get();
}
//求和
Optional<Integer> total3 = listbo.stream().map(userBO::getId).reduce((a,b) -> a + b);
Integer total1 = listbo.stream().map(userBO::getId).reduce(0,(a,b) -> a + b);
Integer total2 = listbo.stream().map(userBO::getId).reduce(Integer::sum).get();
//分組 Map<String, List<userBO>> mapBO = listbo.stream().collect(Collectors.groupingBy(userBO::getName)); mapBO.forEach((key,Value)->{ IntStream intStream = Value.stream().mapToInt(userBO::getId); int sum = intStream.sum();//求和 });
