繼續上次的話題,阿粉昨天帶着大傢伙看了 Lambda表達式,是不是感覺真香,哈哈哈,今天這個絕對是更香的存在,因爲之前因爲阿粉用JDK7寫的代碼,還沒老宮吐槽了很久,他是什麼呢?阿粉來帶大家看一下。
流是什麼鬼東西
不知道大家眼中的流是什麼,大家知道官方是怎麼說的麼?
流是Java API的新成員,它允許你以聲明性方式處理數據集合(通過查詢語句來表達,而不是臨時編寫一個實現)。
就現在來說,你可以把它們看成遍歷數據集的高級迭代器。。此 外,流還可以透明地並行處理,你無需寫任何多線程代碼了!現在阿粉先帶大家來看使用流,後邊阿粉還會帶大家來仔細的分析流和並行化。
下面我們來看看使用流的好處吧。
需求: 返回低 熱量的菜餚名稱
之前在Java7中的代碼
List<Dish> lowCaloricDishes = new ArrayList<>();
for(Dish d: menu){
if(d.getCalories() < 400){ //用累加器篩選元素
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()); //處理排序後的菜名列表
}
在阿粉寫這段代碼的時候,老宮當時看到就懟我了,你寫的什麼玩意,垃圾代碼,竟然還有一個垃圾的變量 lowCaloricDishes。
在這段代碼中 lowCaloricDishes 它唯一的作用就是作爲一次性的中間容器。
於是出現了下面一幕,一個爭着給我安利黑科技的人,直接把阿粉拉起來,自己坐下來給我改代碼,大家能腦補一下那個畫面麼?
改良之後的Java8:
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
List<String> lowCaloricDishesName =
menu.stream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
//並且這個可恨的傢伙告訴我,爲了利用多核架構並行執行這段代碼,還可以把stream()換成parallelStream():
List<String> lowCaloricDishesName =
menu.parallelStream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
大家仔細看這個代碼了麼?發現什麼優點了麼?沒有的話我來給大家說:
-
代碼是以聲明性方式寫的:說明想要完成什麼(篩選熱量低的菜餚)而不是說明如 何實現一個操作(利用循環和if條件等控制流語句)。這種方法加上行爲參數化讓你可以輕鬆應對變化的需求:你很容易再創建一個代 碼版本,利用Lambda表達式來篩選高卡路里的菜餚,而用不着去複製粘貼代碼。
-
你可以把幾個基礎操作鏈接起來,來表達複雜的數據處理流水線(在filter後面接 上sorted、map和collect操作,同時保持代碼清晰可讀。
-
filter的結果被傳給了sorted方法,再傳給map方法,最後傳給collect方法。
在阿粉眼中,新的Stream API表達能力簡直不要太強啊。
那我們是不是可以總結一下這個流到底是什麼了?其實說白了,流就是從支持數據處理操作的源生成的元素序列。
-
元素序列:
就像集合一樣,流也提供了一個接口,可以訪問特定元素類型的一組 有序值。因爲集合是數據結構,所以它的主要目的是以特定的時間/空間複雜度存儲和訪問元素(如ArrayList 與 LinkedList)。但流的目的在於表達計算,比如你前面見到的filter、sorted和map。集合講的是數據,流講的是計算。
-
源
流會使用一個提供數據的源,如集合、數組或輸入/輸出資源。 請注意,從有 序集合生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致。
-
數據處理操作
流的數據處理功能支持類似於數據庫的操作,以及函數式編程語 言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以 順序執行,也可並行執行
2.流的特點
-
流水線——很多流操作本身會返回一個流,這樣多個操作就可以鏈接起來,形成一 個大的流水線。這讓我們下一章中的一些優化成爲可能,如延遲和短路。流水線的 操作可以看作對數據源進行數據庫式查詢。
-
內部迭代——與使用迭代器顯式迭代的集合不同,流的迭代操作是在背後進行的。
我們來看一段代碼:
import static java.util.stream.Collectors.toList;
List<String> threeHighCaloricDishNames =
menu.stream() //從menu獲得流(菜餚列表)
.filter(d -> d.getCalories() > 300) //建立操作流水線:首先選出高熱量的菜餚
.map(Dish::getName) //獲取菜名
.limit(3) //只選擇頭三個
.collect(toList()); //將結果保存在另一個List中
System.out.println(threeHighCaloricDishNames);
在本例中,我們先是對menu調用stream方法,由菜單得到一個流。數據源是菜餚列表 (菜單),它給流提供一個元素序列。接下來,對流應用一系列數據處理操作:filter、map、limit和collect。除了collect之外,所有這些操作都會返回另一個流,這樣它們就可以接成一條流水線,於是就可以看作對源的一個查詢。
最後,collect操作開始處理流水線,並返回結果(它和別的操作不一樣,因爲它返回的不是流,在這裏是一個List)。在調用collect之前,沒有任何結果產生,實際上根本就 沒有從menu裏選擇元素。你可以這麼理解:鏈中的方法調用都在排隊等待,直到調用collect。
-
filter——接受Lambda,從流中排除某些元素。在本例中,通過傳遞lambda d -> d.getCalories() > 300,選擇出熱量超過300卡路里的菜餚。
-
map——接受一個Lambda,將元素轉換成其他形式或提取信息。在本例中,通過傳遞 方法引用Dish::getName,相當於Lambda d -> d.getName(),提取了每道菜的菜名
-
limit——截斷流,使其元素不超過給定數量。
-
collect——將流轉換爲其他形式。在本例中,流被轉換爲一個列表。它看起來有點 兒像變魔術,我們在第6章中會詳細解釋collect的工作原理。現在,你可以把 collect看作能夠接受各種方案作爲參數,並將流中的元素累積成爲一個彙總結果的 操作。這裏的toList()就是將流轉換爲列表的方案。
阿粉在這裏再問大家一句,香不香?你就看看這代碼量,香不香,想不想學習,想學的話繼續關注我們呦,鴨血粉絲將會給大家繼續帶來好看的乾貨。
在文章最後,阿粉給大家放一些經常用的。
forEach
Stream 提供了新的方法 ‘forEach’ 來迭代流中的每個數據。以下代碼片段使用 forEach 輸出了10個隨機數:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
map
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 獲取對應的平方數
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
filter
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 獲取空字符串的數量
long count = strings.stream().filter(string -> string.isEmpty()).count();
limit
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);