深入理解Stream之原理剖析
今天我們先來(lái)聊聊深入理解Stream之原理剖析。
Stream操作分類
Stream中的操作可以分為兩大類:中間操作與結(jié)束操作。
中間操作只會(huì)進(jìn)行操作記錄,只有結(jié)束操作才會(huì)觸發(fā)實(shí)際的計(jì)算,可以理解為懶加載,這也是Stream在操作大對(duì)象迭代計(jì)算的時(shí)候如此高效的原因之一。
中間操作分為有狀態(tài)操作與無(wú)狀態(tài)操作,無(wú)狀態(tài)是指元素的處理不受之前元素的影響,有狀態(tài)是指該操作只有拿到所有元素之后才能繼續(xù)下去。這也比較好理解,比如有狀態(tài)的distinct()去重方法,你說(shuō)他能不關(guān)心其他值嗎?當(dāng)然不能,他必須拿到所有元素才知道當(dāng)前迭代的元素是否被重復(fù)。
結(jié)束操作可以分為短路與非短路操作,這個(gè)應(yīng)該很好理解,短路是指遇到某些符合條件的元素就可以得到最終結(jié)果;而非短路是指必須處理所有元素才能得到最終結(jié)果。
之所以要進(jìn)行如此精細(xì)的劃分,是因?yàn)榈讓訉?duì)每一種情況的處理方式不同。
Stream結(jié)構(gòu)分析
讓我們先簡(jiǎn)單看看下面一段代碼:
List<String> list = new ArrayList<>();
// 獲取stream1
Stream<String> stream1 = list.stream();
// stream1通過(guò)filter后得到stream2
Stream<String> stream2 = stream1.filter("lige"::equals);
// stream1與stream2是同一個(gè)對(duì)象嗎?
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());
// 結(jié)果
// stream1.equals(stream2) = false
// stream1.classTypeName = java.util.stream.ReferencePipeline$Head
// stream1.classTypeName = java.util.stream.ReferencePipeline$2
很明顯,stream1與stream2不是同一個(gè)對(duì)象,并且他們不是同一個(gè)實(shí)現(xiàn)類。stream1的實(shí)現(xiàn)類為ReferencePipeline$Head,而stream2的實(shí)現(xiàn)類為一個(gè)匿名內(nèi)部類,讓我們進(jìn)步一分析其源碼,所謂源碼之下,無(wú)所遁形。
讓我們?cè)倏纯磗tream2:
通過(guò)分析我們可以發(fā)現(xiàn),stream2的實(shí)現(xiàn)類是StatelessOp,所以就形成了這樣一個(gè)結(jié)構(gòu)。
每一次中間操作都會(huì)生成一個(gè)新的Stream,如果是無(wú)狀態(tài)操作則實(shí)現(xiàn)類是StatelessOp,如果是有狀態(tài)操作則實(shí)現(xiàn)類是StatefulOp。
讓我們?cè)賮?lái)看一下他們之間的繼承關(guān)系。
再聊核心Sink
實(shí)際上Stream API內(nèi)部實(shí)現(xiàn)的的本質(zhì),就是如何重載Sink的這四個(gè)接口方法。
我還是從一個(gè)示例開(kāi)始:
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的數(shù)據(jù)
.filter(it -> it.contains("lige"))// 2. 只要包含lige的數(shù)據(jù)
.map(String::toUpperCase)// 3. 對(duì)符合的數(shù)據(jù)作進(jìn)一步加工,轉(zhuǎn)換大寫(xiě)
.map(String::toLowerCase)// 4. 對(duì)符合的數(shù)據(jù)作進(jìn)一步加工,轉(zhuǎn)換小寫(xiě)
.collect(Collectors.toList());
resultList.forEach(System.out::println);
不管是filter方法,還是map方法,還是其他的方法,我們進(jìn)入到源碼層面,返回了一個(gè)StatelessOp對(duì)象或StatefulOp對(duì)象。
所以便產(chǎn)生了這樣一個(gè)結(jié)構(gòu):
但是和Sink有什么關(guān)系呢?我們?cè)俜催^(guò)來(lái)看filter或者map源碼:
直接返回一個(gè)匿名StatelessOp對(duì)象,實(shí)現(xiàn)opWrapSink方法,opWrapSink方法是傳入一個(gè)sink對(duì)象,返回另一個(gè)sink對(duì)象。而新的sink對(duì)象擁有傳入sink對(duì)象的引用。
但是,這個(gè)代碼有什么用?什么時(shí)候觸發(fā)的呢?
別著急,讓我們從collect(Collectors.toList())方法開(kāi)始一步一步深入研究。
這里我們需要知道傳入xx方法的終端對(duì)象是ReduceOp,并且這個(gè)ReduceOp對(duì)象在makeSink的時(shí)候返回了一個(gè)匿名內(nèi)部類ReducingSink對(duì)象。
這里的makeSink我們提到過(guò),返回一個(gè)匿名內(nèi)部類ReducingSink對(duì)象。
先執(zhí)行warpSink,再執(zhí)行copyInto。直白一點(diǎn)就是先對(duì)Sink進(jìn)行包裝成鏈?zhǔn)絊ink,再遍歷Sink鏈進(jìn)行copy到結(jié)果對(duì)象里。這里的兩個(gè)步驟都很核心。
先看warpSink:
首次進(jìn)入時(shí),this為最后的Stream對(duì)象,從尾部向頭部遍歷
每次遍歷時(shí),得到一個(gè)新的Stream對(duì)象,一般為StatelessOp對(duì)象或StatefulOp對(duì)象
執(zhí)行操作對(duì)象的opWrapSink方法,這就是匿名實(shí)現(xiàn)了。
在每一個(gè)opWrapSink實(shí)現(xiàn)方法中,傳入了上一個(gè)sink,最終得到一個(gè)sink鏈表
最后,返回Sink鏈的頭節(jié)點(diǎn),內(nèi)部稱之為包裝好的sink,命名wrapped,隨后,準(zhǔn)備進(jìn)行執(zhí)行begin,forEachRemaining,end方法。
forEachRemaning最終調(diào)用accept方法。
動(dòng)畫(huà)理解Stream執(zhí)行流程
? ?