【Java8實戰】流的引入

引言

集合是Java中使用多的API。但在java8之前,我們對於集合的操作大多需要涉及數據庫,比如對於某個集合做篩選,都是通過sql中的where條件去實現。我們能不能不借助sql,僅僅通過操作集合就能實現?另外,在處理大量數據的情況下,又要保證程序的性能,我們的代碼又該如何去寫?

在Java8中,引入了流的概念,可以解決上述兩個問題。

流是什麼

流是Java API的新成員,它允許你以聲明性方式處理數據集合,可以把它們看成遍歷數據集的高級迭代器。此外,流還可以透明地並行處理,我們無需寫任何多線程代碼。

示例對比

用代碼實現篩選低熱量的食物,並按照卡路里排序,我們可以用Java7和Java8實現,對比一下。

Java7代碼實現:

List<Dish> lowCaloricDishes = new ArrayList<>();
//遍歷篩選卡路里低於200的食物
for(Dish d: dishes)
{
    if(d.getCalories() < 200){
        lowCaloricDishes.add(d);
    }
}
//將篩選出來的食物按卡路里高低排序
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
    public int compare(Dish d1, Dish d2){
        return Integer.compare(d1.getCalories(), d2.getCalories());
    }
});
List<String> lowCaloricDishesName = new ArrayList<>();
//遍歷得到低卡路里的食物名稱
for(Dish d: lowCaloricDishes){
    lowCaloricDishesName.add(d.getName());
}

Java8代碼實現:

List<String> lowCaloricDishesName =  dishes.stream()
                                    .filter(d -> d.getCalories() < 200)
                                    .sorted(comparing(Dish::getCalories))
                                    .map(Dish::getName).collect(toList());

從以上代碼中,我們可以發現Java8方式實現的一些好處:

(1) 代碼是以聲明性方式寫的:說明想要完成什麼(篩選熱量低的菜餚)而不是說明如何實現一個操作(利用循環和if條件等控制流語句)。

(2) 加上行爲參數化讓你可以輕鬆應對變化的需求:我們很容易再創建一個代碼版本,利用 Lambda表達式來篩選高卡路里的菜餚,而用不着去複製粘貼代碼。

(3) 把幾個基礎操作鏈接起來,來表達複雜的數據處理流水線(在filter後面接上 sorted、map和collect操作 ),同時保持代碼清晰可讀。

流的定義

流到底是什麼呢?簡短的定義就是“從支持數據處理操作生成的元素序列”。

(1) 元素序列——就像集合一樣,流也提供了一個接口,可以訪問特定元素類型的一組有序值。

(2) ——流會使用一個提供數據的源,如集合、數組或輸入/輸出資源。 從有序集合生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致。

(3) 數據處理操作——流的數據處理功能支持類似於數據庫的操作,以及函數式編程語言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以順序執行,也可並行執行。

流操作的特點

(1) 流水線——很多流操作本身會返回一個流,這樣多個操作就可以鏈接起來,形成一個大 的流水線。

(2) 內部迭代——與使用迭代器顯式迭代的集合不同,流的迭代操作是在背後進行的。

流與集合

集合是一個內存中的數據結構, 它包含數據結構中目前所有的值——集合中的每個元素都得先算出來才能添加到集合中。(可以往集合里加東西或者刪東西,但是不管什麼時候,集合中的每個元素都是放在內存裏的,元素都得先算出來才能成爲集合的一部分。)

流則是在概念上固定的數據結構不能添加或刪除元素),其元素則是按需計算的。這個思想就是用戶僅僅從流中提取需要的值,而這些值——在用 戶看不見的地方——只會按需生成。

和迭代器類似,流只能遍歷一次。遍歷完之後,我們就說這個流已經被消費掉了。 你可以從原始數據源那裏再獲得一個新的流來重新遍歷一遍,就像迭代器一樣。

示例代碼:

List<String> title = Arrays.asList("Java8", "In", "Action"); 
Stream<String> s = title.stream(); 
//Java8
//In
//Action
s.forEach(System.out::println);  
//java.lang.IllegalStateException: stream has already been operated upon or closed
s.forEach(System.out::println); 

外部迭代和內部迭代

在這裏插入圖片描述

使用Collection接口需要用戶去做迭代(比如用for-each),這稱爲外部迭代

示例代碼:

List<String> names = new ArrayList<>();
for(Dish d: menu)
{      
	names.add(d.getName()); 
} 

相反, Streams庫使用內部迭代——它幫你把迭代做了,還把得到的流值存在了某個地方,你只要給出 一個函數說要幹什麼就可以了。

示例代碼:

List<String> names = menu.stream()                          
					     .map(Dish::getName)                           
						 .collect(toList()); 

流的兩類操作

(1) 中間操作——可以連接起來的流操作。例如:filter、map和limit可以連成一條流水線;

(2) 終端操作——關閉流的操作。例如:collect觸發流水線執行並關閉它。

在這裏插入圖片描述

使用流

流的使用一般包括三件事:

(1) 一個數據源(如集合)來執行一個查詢;

(2) 一箇中間操作鏈,形成一條流的流水線;

(3) 一個終端操作,執行流水線,並能生成結果。

總結

本篇博客主要是從理論上對流有了更深入的瞭解,並且與常操作的集合做了一些比較。通過java7與java8的代碼示例對比,我們也可以很明顯的感受到流的引入給我們帶來的好處,總結一下,Java 8中的Stream API可以讓我們這樣的代碼:

(1) 聲明性——更簡潔,更易讀

(2) 可複合——更靈活

(3) 可並行——性能更好

下篇博客會通過代碼來學習如何用流進行一系列操作,從而寫出更優秀的代碼。

發佈了343 篇原創文章 · 獲贊 240 · 訪問量 61萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章