java 流式_Java开发笔记(七十二)Java8新增的流式处理
通過(guò)前面幾篇文章的學(xué)習(xí),大家應(yīng)能掌握幾種容器類(lèi)型的常見(jiàn)用法,對(duì)于簡(jiǎn)單的增刪改和遍歷操作,各容器實(shí)例都提供了相應(yīng)的處理方法,對(duì)于實(shí)際開(kāi)發(fā)中頻繁使用的清單List,還能利用Arrays工具的asList方法給清單對(duì)象做初始化賦值,另外提供了專(zhuān)門(mén)的Collections工具進(jìn)行排序、求最大元素、求最小元素等操作。那么涉及到更加復(fù)雜的數(shù)據(jù)處理,游蕩如何有針對(duì)性地篩選和進(jìn)一步加功能?
依次遍歷目標(biāo)容器,對(duì)所有元素逐個(gè)加以分析判斷,并酌情將具體數(shù)據(jù)調(diào)整至滿意的狀態(tài),這種千篇一律的業(yè)務(wù)流程固然能夠解決問(wèn)題,可惜由此帶來(lái)的副作用是顯而易見(jiàn)的,包括但不限于:代碼冗長(zhǎng)、分支眾多、邏輯繁瑣、不易重用等等。為了改進(jìn)相關(guān)業(yè)務(wù)邏輯的編程方式,幫助開(kāi)發(fā)者形成良好的編碼風(fēng)格,Java的每次版本更新都試圖給出有效的解決方案,其中影響深遠(yuǎn)的當(dāng)數(shù)Java8推出的兩項(xiàng)新特性:新增的泛型接口與流式處理。關(guān)于前一個(gè)泛型接口特性,用于容器操作的泛型接口主要有三個(gè),分別是斷言接口、消費(fèi)接口和函數(shù)接口,有關(guān)的應(yīng)用案例可參見(jiàn)之前的泛型接口文章,這里不再贅述。真正具有革命性意義的才是本文的主角——流式處理。
所謂流,隱含著流水線的意思,也就是由開(kāi)發(fā)者事先設(shè)定一批處理指令,說(shuō)明清楚每條指令的前因后果,然后啟動(dòng)流水線作業(yè),即可得到最終的處理結(jié)果。流式處理的精髓在于一氣呵成,只要萬(wàn)事俱備,決不拖泥帶水。開(kāi)展流式處理主要包括三個(gè)步驟:獲得容器的流對(duì)象、設(shè)置流的各項(xiàng)篩選和加工指令,以及規(guī)劃處理結(jié)果的展示形式。下面就分別予以詳細(xì)介紹。
1、獲得容器的流對(duì)象
Java8給每種容器都準(zhǔn)備了兩條流水線,一條是串行流,另一條是并行流。串行流顧名思義各項(xiàng)任務(wù)是前后串在一起的,只有處理完前一項(xiàng)任務(wù),才能繼續(xù)執(zhí)行后一項(xiàng)任務(wù)。調(diào)用容器實(shí)例的stream方法即可獲得該容器的串行流對(duì)象,而調(diào)用容器實(shí)例的parallelStream方法可獲得該容器的并行流對(duì)象。
流對(duì)象的獲取操作同時(shí)也是流式處理的開(kāi)始指令,每次進(jìn)行流式處理之前,都必須先獲取當(dāng)前容器的流對(duì)象,要么獲取串行流,要么獲取并行流。
2、設(shè)置流的各項(xiàng)篩選和加工指令
不管是串行流還是并行流,它們承載的都是容器內(nèi)部的原始數(shù)據(jù),這些原材料要經(jīng)過(guò)各道加工工序,之后才會(huì)得到具備初步形態(tài)的半成品。加工數(shù)據(jù)期間所調(diào)用的流方法說(shuō)明如下:
filter:按照指定條件過(guò)濾。即篩選出符合條件的那部分?jǐn)?shù)據(jù)。
sorted:根據(jù)指定字段對(duì)所有記錄排序。可選擇升序或者降序。
map:映射成指定的數(shù)據(jù)類(lèi)型。
limit:只取前面若干條數(shù)據(jù)。
distinct:去掉重復(fù)記錄。保證每條記錄都是唯一的。
以上的加工方法屬于流式處理的中間指令,每次流水線作業(yè)都允許設(shè)置一條或者多條中間指令。
3、規(guī)劃處理結(jié)果的展示形式
前一步的各項(xiàng)加工處理完畢,還要弄個(gè)包裝才能輸出最終的成品,也就是這條流水線生產(chǎn)出來(lái)的數(shù)據(jù)到底長(zhǎng)什么模樣。結(jié)果數(shù)據(jù)的記錄包裝有三種形式,分別對(duì)應(yīng)如下的三個(gè)方法:
count:統(tǒng)計(jì)結(jié)果數(shù)據(jù)的數(shù)量。
forEach:依次遍歷結(jié)果數(shù)據(jù),并逐條進(jìn)行個(gè)性化處理。
collect:搜集和整理結(jié)果數(shù)據(jù),并返回指定格式的清單記錄。
上面的三個(gè)包裝方法屬于流式處理的結(jié)束指令,每次流水線作業(yè)必須配備有且僅有其中的一條結(jié)束指令。
接下來(lái)列舉幾個(gè)實(shí)際應(yīng)用的業(yè)務(wù)場(chǎng)景,看看采取流式處理時(shí)該如何編碼。首先準(zhǔn)備一個(gè)原始的蘋(píng)果清單,后續(xù)將對(duì)這個(gè)蘋(píng)果清單發(fā)動(dòng)流水作業(yè)。原始清單的獲取代碼示例如下:
// 獲取默認(rèn)的蘋(píng)果清單
private static ArrayList getAppleList() {
ArrayList appleList = new ArrayList();
appleList.add(new Apple("紅蘋(píng)果", "RED", 150d, 10d));
appleList.add(new Apple("大蘋(píng)果", "green", 250d, 10d));
appleList.add(new Apple("紅蘋(píng)果", "red", 300d, 10d));
appleList.add(new Apple("大蘋(píng)果", "yellow", 200d, 10d));
appleList.add(new Apple("紅蘋(píng)果", "green", 100d, 10d));
appleList.add(new Apple("大蘋(píng)果", "Red", 250d, 10d));
return appleList;
}
然后需要統(tǒng)計(jì)紅蘋(píng)果總數(shù)的話,可通過(guò)下列的流式代碼開(kāi)展統(tǒng)計(jì)操作:
// 統(tǒng)計(jì)紅蘋(píng)果的總數(shù)
long redCount = getAppleList().stream() // 串行處理
.filter(Apple::isRedApple) // 過(guò)濾條件。專(zhuān)門(mén)挑選紅蘋(píng)果
.count(); // 統(tǒng)計(jì)記錄個(gè)數(shù)
System.out.println("紅蘋(píng)果總數(shù)=" + redCount);
注意到上述代碼的filter方法內(nèi)部出現(xiàn)了方法引用,的確流式處理的主要方法都預(yù)留了函數(shù)式接口的調(diào)用,所以經(jīng)常會(huì)在流式代碼中看到五花八門(mén)的方法引用與Lambda表達(dá)式。比如下面的結(jié)果遍歷代碼就在forEach方法中填充了Lambda表達(dá)式:
// 對(duì)每個(gè)紅蘋(píng)果依次進(jìn)行處理
getAppleList().stream() // 串行處理
.filter(Apple::isRedApple) // 過(guò)濾條件。專(zhuān)門(mén)挑選紅蘋(píng)果
.forEach(s -> System.out.println("當(dāng)前顏色為"+s.getColor())); // 逐條開(kāi)展操作
當(dāng)然流水作業(yè)更常見(jiàn)的輸出另一串清單數(shù)據(jù),此時(shí)流式處理的結(jié)束指令就得采用collect方法。下面便是從原始清單中挑出紅蘋(píng)果清單的流式代碼:
// 挑出紅蘋(píng)果清單
List redAppleList = getAppleList().stream() // 串行處理
//.parallelStream() // 并行處理
.filter(Apple::isRedApple) // 過(guò)濾條件。專(zhuān)門(mén)挑選紅蘋(píng)果
.sorted(Comparator.comparing(Apple::getWeight)) // 按蘋(píng)果重量升序排列
//.sorted(Comparator.comparing(Apple::getWeight).reversed()) // 按蘋(píng)果重量降序排列
.limit(3) // 只取前幾條數(shù)據(jù)
.distinct() // 去掉重復(fù)記錄
.collect(Collectors.toList()); // 返回一串清單
System.out.println("紅蘋(píng)果清單=" + redAppleList.toString());
結(jié)果清單可能不需要完整的蘋(píng)果信息,只需列出蘋(píng)果名稱(chēng)字段,那么得調(diào)用map方法把完整的蘋(píng)果信息映射為單個(gè)的名稱(chēng)字段。此時(shí)的篩選代碼變成下面這樣:
// 挑出去重后的蘋(píng)果名稱(chēng)清單
List allNameList = getAppleList().stream() // 串行處理
.map(Apple::getName) // 映射成新的數(shù)據(jù)類(lèi)型
.distinct() // 去掉重復(fù)記錄
.collect(Collectors.toList()); // 返回一串清單
System.out.println("蘋(píng)果名稱(chēng)去重后的清單=" + allNameList.toString());
除了普通的清單,collect方法還能返回分組清單,也就是把結(jié)果數(shù)據(jù)按照某種條件進(jìn)行分組,再統(tǒng)計(jì)每個(gè)分組的成員數(shù)目。仍以蘋(píng)果清單為例,紅蘋(píng)果可通過(guò)名稱(chēng)或者產(chǎn)地分組,分組的同時(shí)計(jì)算每個(gè)小組里各有多少粒蘋(píng)果。于是形成了以下的分組計(jì)數(shù)代碼:
// 按照名稱(chēng)統(tǒng)計(jì)紅蘋(píng)果的分組個(gè)數(shù)
Map redStatisticCount = getAppleList().stream() // 串行處理
.filter(Apple::isRedApple) // 過(guò)濾條件。專(zhuān)門(mén)挑選紅蘋(píng)果
.collect(Collectors.groupingBy(Apple::getName, Collectors.counting())); // 返回分組計(jì)數(shù)
System.out.println("紅蘋(píng)果分組計(jì)數(shù)=" + redStatisticCount.toString());
分組計(jì)數(shù)僅僅是簡(jiǎn)單統(tǒng)計(jì)各組的成員數(shù)量,有時(shí)還想單獨(dú)計(jì)算某個(gè)字段的統(tǒng)計(jì)值,比如每個(gè)小組里的蘋(píng)果總價(jià)各是多少?這時(shí)collect方法必須同時(shí)完成兩項(xiàng)任務(wù),第一項(xiàng)要根據(jù)某種條件分組,第二項(xiàng)要對(duì)各組的蘋(píng)果價(jià)格求和,如此改造之后的分組求和代碼如下所示:
// 按照名稱(chēng)統(tǒng)計(jì)紅蘋(píng)果的分組總價(jià)
Map redPriceSum = getAppleList().stream() // 串行處理
.filter(Apple::isRedApple) // 過(guò)濾條件。專(zhuān)門(mén)挑選紅蘋(píng)果
.collect(Collectors.groupingBy(Apple::getName, Collectors.summingDouble(Apple::getPrice))); // 返回分組并對(duì)某字段求和
System.out.println("紅蘋(píng)果分組總價(jià)=" + redPriceSum.toString());
觀察以上的具體案例,發(fā)現(xiàn)流式處理的代碼相當(dāng)連貫,每個(gè)步驟該做什么事情都一清二楚,中間沒(méi)有許多繁復(fù)的流程控制,唯有一條條分工明確的處理指令,同時(shí)充分發(fā)揮了方法引用及Lambda表達(dá)式的便利性,使得原本令人頭痛的容器加工變成了有章可循的流水線作業(yè),從而極大地提高了開(kāi)發(fā)者的編碼效率。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的java 流式_Java开发笔记(七十二)Java8新增的流式处理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python mysql 正则表达式,M
- 下一篇: java 类 属性数量_跟我学java编