Java開發筆記(七十二)Java8新增的流式處理

經過前面幾篇文章的學習,你們應能掌握幾種容器類型的常見用法,對於簡單的增刪改和遍歷操做,各容器實例都提供了相應的處理方法,對於實際開發中頻繁使用的清單List,還能利用Arrays工具的asList方法給清單對象作初始化賦值,另外提供了專門的Collections工具進行排序、求最大元素、求最小元素等操做。那麼涉及到更加複雜的數據處理,遊蕩如何有針對性地篩選和進一步加功能?
依次遍歷目標容器,對全部元素逐個加以分析判斷,並酌情將具體數據調整至滿意的狀態,這種千篇一概的業務流程當然可以解決問題,惋惜由此帶來的反作用是顯而易見的,包括但不限於:代碼冗長、分支衆多、邏輯繁瑣、不易重用等等。爲了改進相關業務邏輯的編程方式,幫助開發者造成良好的編碼風格,Java的每次版本更新都試圖給出有效的解決方案,其中影響深遠的當數Java8推出的兩項新特性:新增的泛型接口與流式處理。關於前一個泛型接口特性,用於容器操做的泛型接口主要有三個,分別是斷言接口、消費接口和函數接口,有關的應用案例可參見以前的泛型接口文章,這裏再也不贅述。真正具備革命性意義的纔是本文的主角——流式處理。
所謂流,隱含着流水線的意思,也就是由開發者事先設定一批處理指令,說明清楚每條指令的來龍去脈,而後啓動流水線做業,便可獲得最終的處理結果。流式處理的精髓在於一鼓作氣,只要萬事俱備,決不拖泥帶水。開展流式處理主要包括三個步驟:得到容器的流對象、設置流的各項篩選和加工指令,以及規劃處理結果的展現形式。下面就分別予以詳細介紹。html

一、得到容器的流對象
Java8給每種容器都準備了兩條流水線,一條是串行流,另外一條是並行流。串行流顧名思義各項任務是先後串在一塊兒的,只有處理完前一項任務,才能繼續執行後一項任務。調用容器實例的stream方法便可得到該容器的串行流對象,而調用容器實例的parallelStream方法可得到該容器的並行流對象。
流對象的獲取操做同時也是流式處理的開始指令,每次進行流式處理以前,都必須先獲取當前容器的流對象,要麼獲取串行流,要麼獲取並行流。java

二、設置流的各項篩選和加工指令
不論是串行流仍是並行流,它們承載的都是容器內部的原始數據,這些原材料要通過各道加工工序,以後纔會獲得具有初步形態的半成品。加工數據期間所調用的流方法說明以下:
filter:按照指定條件過濾。即篩選出符合條件的那部分數據。
sorted:根據指定字段對全部記錄排序。可選擇升序或者降序。
map:映射成指定的數據類型。
limit:只取前面若干條數據。
distinct:去掉重複記錄。保證每條記錄都是惟一的。
以上的加工方法屬於流式處理的中間指令,每次流水線做業都容許設置一條或者多條中間指令。編程

三、規劃處理結果的展現形式
前一步的各項加工處理完畢,還要弄個包裝才能輸出最終的成品,也就是這條流水線生產出來的數據到底長什麼模樣。結果數據的記錄包裝有三種形式,分別對應以下的三個方法:
count:統計結果數據的數量。
forEach:依次遍歷結果數據,並逐條進行個性化處理。
collect:蒐集和整理結果數據,並返回指定格式的清單記錄。
上面的三個包裝方法屬於流式處理的結束指令,每次流水線做業必須配備有且僅有其中的一條結束指令。app

接下來列舉幾個實際應用的業務場景,看看採起流式處理時該如何編碼。首先準備一個原始的蘋果清單,後續將對這個蘋果清單發動流水做業。原始清單的獲取代碼示例以下:函數

	// 獲取默認的蘋果清單
	private static ArrayList<Apple> getAppleList() {
		ArrayList<Apple> appleList = new ArrayList<Apple>();
			appleList.add(new Apple("紅蘋果", "RED", 150d, 10d));
			appleList.add(new Apple("大蘋果", "green", 250d, 10d));
			appleList.add(new Apple("紅蘋果", "red", 300d, 10d));
			appleList.add(new Apple("大蘋果", "yellow", 200d, 10d));
			appleList.add(new Apple("紅蘋果", "green", 100d, 10d));
			appleList.add(new Apple("大蘋果", "Red", 250d, 10d));
		return appleList;
	}

 

而後須要統計紅蘋果總數的話,可經過下列的流式代碼開展統計操做:工具

		// 統計紅蘋果的總數
		long redCount = getAppleList().stream() // 串行處理
				.filter(Apple::isRedApple) // 過濾條件。專門挑選紅蘋果
				.count(); // 統計記錄個數
		System.out.println("紅蘋果總數=" + redCount);

 

注意到上述代碼的filter方法內部出現了方法引用,的確流式處理的主要方法都預留了函數式接口的調用,因此常常會在流式代碼中看到五花八門的方法引用與Lambda表達式。好比下面的結果遍歷代碼就在forEach方法中填充了Lambda表達式:學習

		// 對每一個紅蘋果依次進行處理
		getAppleList().stream() // 串行處理
				.filter(Apple::isRedApple) // 過濾條件。專門挑選紅蘋果
				.forEach(s -> System.out.println("當前顏色爲"+s.getColor())); // 逐條開展操做

 

固然流水做業更常見的輸出另外一串清單數據,此時流式處理的結束指令就得采用collect方法。下面即是從原始清單中挑出紅蘋果清單的流式代碼:編碼

		// 挑出紅蘋果清單
		List<Apple> redAppleList = getAppleList().stream() // 串行處理
				//.parallelStream() // 並行處理
				.filter(Apple::isRedApple) // 過濾條件。專門挑選紅蘋果
				.sorted(Comparator.comparing(Apple::getWeight)) // 按蘋果重量升序排列
				//.sorted(Comparator.comparing(Apple::getWeight).reversed()) // 按蘋果重量降序排列
				.limit(3) // 只取前幾條數據
				.distinct() // 去掉重複記錄
				.collect(Collectors.toList()); // 返回一串清單
		System.out.println("紅蘋果清單=" + redAppleList.toString());

 

結果清單可能不須要完整的蘋果信息,只需列出蘋果名稱字段,那麼得調用map方法把完整的蘋果信息映射爲單個的名稱字段。此時的篩選代碼變成下面這樣:spa

		// 挑出去重後的蘋果名稱清單
		List<String> allNameList = getAppleList().stream() // 串行處理
				.map(Apple::getName) // 映射成新的數據類型
				.distinct() // 去掉重複記錄
				.collect(Collectors.toList()); // 返回一串清單
		System.out.println("蘋果名稱去重後的清單=" + allNameList.toString());

 

除了普通的清單,collect方法還能返回分組清單,也就是把結果數據按照某種條件進行分組,再統計每一個分組的成員數目。仍以蘋果清單爲例,紅蘋果可經過名稱或者產地分組,分組的同時計算每一個小組裏各有多少粒蘋果。因而造成了如下的分組計數代碼:htm

		// 按照名稱統計紅蘋果的分組個數
		Map<String, Long> redStatisticCount = getAppleList().stream() // 串行處理
				.filter(Apple::isRedApple) // 過濾條件。專門挑選紅蘋果
				.collect(Collectors.groupingBy(Apple::getName, Collectors.counting())); // 返回分組計數
		System.out.println("紅蘋果分組計數=" + redStatisticCount.toString());

 

分組計數僅僅是簡單統計各組的成員數量,有時還想單獨計算某個字段的統計值,好比每一個小組裏的蘋果總價各是多少?這時collect方法必須同時完成兩項任務,第一項要根據某種條件分組,第二項要對各組的蘋果價格求和,如此改造以後的分組求和代碼以下所示:

		// 按照名稱統計紅蘋果的分組總價
		Map<String, Double> redPriceSum = getAppleList().stream() // 串行處理
				.filter(Apple::isRedApple) // 過濾條件。專門挑選紅蘋果
				.collect(Collectors.groupingBy(Apple::getName, Collectors.summingDouble(Apple::getPrice))); // 返回分組並對某字段求和
		System.out.println("紅蘋果分組總價=" + redPriceSum.toString());

 

觀察以上的具體案例,發現流式處理的代碼至關連貫,每一個步驟該作什麼事情都一清二楚,中間沒有許多繁複的流程控制,惟有一條條分工明確的處理指令,同時充分發揮了方法引用及Lambda表達式的便利性,使得本來使人頭痛的容器加工變成了有章可循的流水線做業,從而極大地提升了開發者的編碼效率。



更多Java技術文章參見《Java開發筆記(序)章節目錄

相關文章
相關標籤/搜索