簡潔又快速地處理集合——java8 Stream(上)

Java 8 發佈至今也已經好幾年過去,現在 Java 也已經向 11 邁去,可是 Java 8 做出的改變能夠說是革命性的,影響足夠深遠,學習 Java 8 應該是 Java 開發者的必修課。git

今天給你們帶來 Java 8 Stream 講解,爲何直接講這個,是由於只要你學完,馬上就能上手,並能讓它在你的代碼中大展身手。程序員

值得注意的是:學習 Stream 以前必須先學習 lambda 的相關知識。本文也假設讀者已經掌握 lambda 的相關知識。github

本篇文章主要內容:數據庫

  • 介紹 Stream 以及 Stream 是如何處理集合的
  • 介紹 Stream 與集合的關係與區別
  • Stream 的基本方法介紹

一. 什麼是 Stream數據結構

Stream 中文稱爲 「流」,經過將集合轉換爲這麼一種叫作 「流」 的元素序列,經過聲明性方式,可以對集合中的每一個元素進行一系列並行或串行的流水線操做。app

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

二. 流操做搜索引擎

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

其中數據源即是原始集合,而後將如 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<String> stream = list.stream().map(Person::getName).sorted().limit(10);         
List<String> newList = stream.collect(toList());
List<String> newList2 = stream.collect(toList());
複製代碼

上面代碼中第三行會報錯,由於第二行已經使用過這個流,這個流已經被消費掉了

四. 方法介紹,開始實戰

首先咱們先建立一個 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() 是並行流方法,可以讓數據集執行並行操做,後面會更詳細地講解

  1. 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 類型,這個之後會講解

  1. distinct()

去除重複元素,這個方法是經過類的 equals 方法來判斷兩個元素是否相等的

如例子中的 Person 類,須要先定義好 equals 方法,否則相似[Person{name='jack', age=20}, Person{name='jack', age=20}] 這樣的狀況是不會處理的

  1. 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());
複製代碼
  1. limit(long n)

返回前 n 個元素

list = list.stream()
           .limit(2)
           .collect(toList());
//打印輸出 [Person{name='jack', age=20}, Person{name='mike', age=25}]
複製代碼
  1. 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}]

  1. map(T -> R)

將流中的每個元素 T 映射爲 R(相似類型轉換)

List<String> newlist = 
list.stream().map(Person::getName).collect(toList());
複製代碼

newlist 裏面的元素爲 list 中每個 Person 對象的 name 變量

  1. 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,由於 split 方法返回的是 String[ ] 類型;因此咱們須要使用 flatMap 方法,先使用Arrays::stream將每一個 String[ ] 元素變成一個 Stream流,而後 flatMap 會將每個流鏈接成爲一個流,最終返回咱們須要的 Stream

  1. anyMatch(T -> boolean)

流中是否有一個元素匹配給定的 T -> boolean 條件

//是否存在一個 person 對象的 age 等於 20:
boolean b = list.stream().anyMatch(person -> person.getAge() == 20);
複製代碼
  1. allMatch(T -> boolean)

流中是否全部元素都匹配給定的 T -> boolean 條件

  1. noneMatch(T -> boolean)

流中是否沒有元素匹配給定的 T -> boolean 條件

  1. findAny() 和 findFirst()
  • findAny():找到其中一個元素 (使用 stream() 時找到的是第一個元素;使用 parallelStream() 並行時找到的是其中一個元素)
  • findFirst():找到第一個元素

值得注意的是,這兩個方法返回的是一個 Optional**對象,它是一個容器類,能表明一個值存在或不存在,這個後面會講到

  1. 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<Integer> sum = list.stream().map(Person::getAge).reduce(Integer::sum);
複製代碼

即不接受任何起始值,但由於沒有初始值,須要考慮結果可能不存在的狀況,所以返回的是 Optional 類型。

  1. count()

返回流中元素個數,結果爲 long 類型

  1. collect()

收集方法,咱們很經常使用的是 collect(toList()),固然還有 collect(toSet()) 等,參數是一個收集器接口,這個後面會另外講。

  1. forEach()

返回結果爲 void,很明顯咱們能夠經過它來幹什麼了,比方說:

//### 16. unordered()還有這個比較不起眼的方法,
//#返回一個等效的無序流,固然若是流自己就是無序的話,那可能就會直接返回其自己
//打印各個元素:
list.stream().forEach(System.out::println);
複製代碼

再好比說 MyBatis 裏面訪問數據庫的 mapper 方法:

//向數據庫插入新元素:
list.stream().forEach(PersonMapper::insertPerson);
複製代碼

做者:Howie_Y

主頁:www.jianshu.com/u/79638e5f0743/small>


歡迎關注公衆號:

https://user-gold-cdn.xitu.io/2019/9/9/16d13739553edb8a?w=2800&h=800&f=jpeg&s=155674
相關文章
相關標籤/搜索