天天看點

從原理剖析帶你了解Stream

摘要:Stream是jdk1.8給我們提供的新特性

本文分享自華為雲社群《深入了解Stream之原理剖析》,作者: 李哥技術 。

Stream是jdk1.8給我們提供的新特性,主要就是允許我們采用聲明式的方式處理資料集合,我們要知道在項目中我們集合就是我們最常用的資料存儲結構,有時後我們需要對集合内的元素做一些過濾或者其他的操作我們一般是采用for循環的方式。

Stream操作分類

從原理剖析帶你了解Stream

Stream中的操作可以分為兩大類:中間操作與結束操作。

中間操作隻會進行操作記錄,隻有結束操作才會觸發實際的計算,可以了解為懶加載,這也是Stream在操作大對象疊代計算的時候如此高效的原因之一。

中間操作分為有狀态操作與無狀态操作,無狀态是指元素的處理不受之前元素的影響,有狀态是指該操作隻有拿到所有元素之後才能繼續下去。這也比較好了解,比如有狀态的distinct()去重方法,你說他能不關心其他值嗎?當然不能,他必須拿到所有元素才知道目前疊代的元素是否被重複。

結束操作可以分為短路與非短路操作,這個應該很好了解,短路是指遇到某些符合條件的元素就可以得到最終結果;而非短路是指必須處理所有元素才能得到最終結果。

之是以要進行如此精細的劃分,是因為底層對每一種情況的處理方式不同。

Stream結構分析

讓我們先簡單看看下面一段代碼:

List<String> list = new ArrayList<>();
 // 擷取stream1
 Stream<String> stream1 = list.stream();
 // stream1通過filter後得到stream2
 Stream<String> stream2 = stream1.filter("lige"::equals);
 // stream1與stream2是同一個對象嗎?
 System.out.println("stream1.equals(stream2) = " + stream1.equals(stream2));
 System.out.println("stream1.classTypeName = " + stream1.getClass().getTypeName());
 System.out.println("stream2.classTypeName = " + stream2.getClass().getTypeName());
 // 結果
 // stream1.equals(stream2) = false
 // stream1.classTypeName = java.util.stream.ReferencePipeline$Head
 // stream1.classTypeName = java.util.stream.ReferencePipeline$2      

很明顯,stream1與stream2不是同一個對象,并且他們不是同一個實作類。stream1的實作類為ReferencePipeline$Head,而stream2的實作類為一個匿名内部類,讓我們進步一分析其源碼,所謂源碼之下,無所遁形。

從原理剖析帶你了解Stream
從原理剖析帶你了解Stream
從原理剖析帶你了解Stream

讓我們再看看stream2:

從原理剖析帶你了解Stream
從原理剖析帶你了解Stream
從原理剖析帶你了解Stream
從原理剖析帶你了解Stream
從原理剖析帶你了解Stream
從原理剖析帶你了解Stream

通過分析我們可以發現,stream2的實作類是StatelessOp,是以就形成了這樣一個結構。

從原理剖析帶你了解Stream

每一次中間操作都會生成一個新的Stream,如果是無狀态操作則實作類是StatelessOp,如果是有狀态操作則實作類是StatefulOp。

讓我們再來看一下他們之間的繼承關系。

從原理剖析帶你了解Stream
從原理剖析帶你了解Stream

再聊核心Sink

實際上Stream API内部實作的的本質,就是如何重載Sink的這四個接口方法。

我還是從一個示例開始:

List<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("ligeligeligeligeligeligeligeligeligelige");
list.add("lisilisilisilisilisilisilisilisi");
list.add("wangwu");
list.add("ligejishuligejishuligejishuligejishuligejishuligejishuligejishu");
List<String> resultList = list.stream()
 .filter(it -> it.contains("li"))// 1. 隻要包含li的資料
 .filter(it -> it.contains("lige"))// 2. 隻要包含lige的資料
 .map(String::toUpperCase)// 3. 對符合的資料作進一步加工,轉換大寫
 .map(String::toLowerCase)// 4. 對符合的資料作進一步加工,轉換小寫
 .collect(Collectors.toList());
resultList.forEach(System.out::println);      

不管是filter方法,還是map方法,還是其他的方法,我們進入到源碼層面,傳回了一個StatelessOp對象或StatefulOp對象。

是以便産生了這樣一個結構:

從原理剖析帶你了解Stream

但是和Sink有什麼關系呢?我們再反過來看filter或者map源碼:

從原理剖析帶你了解Stream

直接傳回一個匿名StatelessOp對象,實作opWrapSink方法,opWrapSink方法是傳入一個sink對象,傳回另一個sink對象。而新的sink對象擁有傳入sink對象的引用。

但是,這個代碼有什麼用?什麼時候觸發的呢?

别着急,讓我們從collect(Collectors.toList())方法開始一步一步深入研究。

從原理剖析帶你了解Stream
從原理剖析帶你了解Stream
從原理剖析帶你了解Stream

這裡我們需要知道傳入xx方法的終端對象是ReduceOp,并且這個ReduceOp對象在makeSink的時候傳回了一個匿名内部類ReducingSink對象。

從原理剖析帶你了解Stream
從原理剖析帶你了解Stream
從原理剖析帶你了解Stream

這裡的makeSink我們提到過,傳回一個匿名内部類ReducingSink對象。

從原理剖析帶你了解Stream

先執行warpSink,再執行copyInto。直白一點就是先對Sink進行包裝成鍊式Sink,再周遊Sink鍊進行copy到結果對象裡。這裡的兩個步驟都很核心。

先看warpSink:

從原理剖析帶你了解Stream
  1. 首次進入時,this為最後的Stream對象,從尾部向頭部周遊
  2. 每次周遊時,得到一個新的Stream對象,一般為StatelessOp對象或StatefulOp對象
  3. 執行操作對象的opWrapSink方法,這就是匿名實作了。
  4. 在每一個opWrapSink實作方法中,傳入了上一個sink,最終得到一個sink連結清單
從原理剖析帶你了解Stream

最後,傳回Sink鍊的頭節點,内部稱之為包裝好的sink,命名wrapped,随後,準備進行執行begin,forEachRemaining,end方法。

從原理剖析帶你了解Stream

forEachRemaning最終調用accept方法。

從原理剖析帶你了解Stream

動畫了解Stream執行流程

從原理剖析帶你了解Stream

點選關注,第一時間了解華為雲新鮮技術~