Java8 Stream

一. 什麼是 Stream

Stream 中文稱爲 「流」,是Java8新特性主要是用來處理集合數據的,能夠將其看做一個高級迭代器,經過將集合轉換爲這麼一種叫作 「流」 的元素序列,經過聲明性方式,可以對集合中的每一個元素進行一系列並行或串行的流水線操做html

換句話說,你只須要告訴流你的要求,流便會在背後自行根據要求對元素進行處理,而你只須要 「不勞而獲」。java

二. 流操做

 
 

整個流操做就是一條流水線,將元素放在流水線上一個個地進行處理。程序員

其中數據源即是原始集合,而後將如 List<T> 的集合轉換爲 Stream<T> 類型的流,並對流進行一系列的中間操做,好比過濾保留部分元素、對元素進行排序、類型轉換等;最後再進行一個終端操做,能夠把 Stream 轉換回集合類型,也能夠直接對其中的各個元素進行處理,好比打印、好比計算總數、計算最大值等等數據庫

很重要的一點是,不少流操做自己就會返回一個流,因此多個操做能夠直接鏈接起來,咱們來看看一條 Stream 操做的代碼: 編程

 

若是是之前,進行這麼一系列操做,你須要作個迭代器或者 foreach 循環,而後遍歷,一步步地親力親爲地去完成這些操做;可是若是使用流,你即可以直接聲明式地下指令,流會幫你完成這些操做。數組

有沒有想到什麼相似的?是的,就像 SQL 語句同樣, select username from user where id = 1,你只要說明:「我須要 id 是 1 (id = 1)的用戶(user)的用戶名(username )」,那麼就能夠獲得本身想要的數據,而不須要本身親自去數據庫裏面循環遍歷查找。數據結構

三. 流與集合

何時計算

Stream 和集合的其中一個差別在於何時進行計算。app

一個集合,它會包含當前數據結構中全部的值,你能夠隨時增刪,可是集合裏面的元素毫無疑問地都是已經計算好了的。dom

流則是按需計算,按照使用者的須要計算數據,你能夠想象咱們經過搜索引擎進行搜索,搜索出來的條目並非所有呈現出來的,並且先顯示最符合的前 10 條或者前 20 條,只有在點擊 「下一頁」 的時候,纔會再輸出新的 10 條。ide

再比方在線觀看電影和你硬盤裏面的電影,也是差很少的道理。

外部迭代和內部迭代

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 類其中其實還有不少學問,講解它說不定也要開一篇文章,這裏先講那麼多,先知道基本怎麼用就能夠。

http://www.javashuo.com/article/p-mabzvyvu-u.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);

打印 23

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)
生成流,爲 01 的隨機雙精度數

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();//求和
});
相關文章
相關標籤/搜索