JDK8的Stream新特性

繼續上次的話題,阿粉昨天帶着大傢伙看了 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);
發佈了71 篇原創文章 · 獲贊 10 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章