還在用迭代器處理集合嗎?試試Stream,真香

前言

上一篇博客一文帶你深刻了解 Lambda 表達式和方法引用我給你們介紹了 Java8 函數式特性中的 Lambda,這篇文章我將繼續討論 stream 流的用法html

聲明:本文首發於博客園,做者:後青春期的Keats;地址:https://www.cnblogs.com/keatsCoder/ 轉載請註明,謝謝!java

Show Time

首先給你們看一段代碼,讓你們直觀感覺下 Java7 和 Java8 遍歷處理集合的不一樣mysql

Dish 是一個菜餚對象,calories 屬性表示該菜品的卡路里值,name 則是菜品的名稱。咱們須要過濾出卡路里小於400、而後根據卡路里值升序、接着拿到他們的名稱列表並返回程序員

Java7sql

public static List<String> getLowCaloricDishesNamesInJava7(List<Dish> dishes){
    List<Dish> lowCaloricDishes = new ArrayList<>();
    for(Dish d: dishes){
        if(d.getCalories() < 400){
            lowCaloricDishes.add(d);
        }
    }
    List<String> lowCaloricDishesName = new ArrayList<>();
    Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
        public int compare(Dish d1, Dish d2){
            return Integer.compare(d1.getCalories(), d2.getCalories());
        }
    });
    for(Dish d: lowCaloricDishes){
        lowCaloricDishesName.add(d.getName());
    }
    return lowCaloricDishesName;
}

Java8app

public static List<String> getLowCaloricDishesNamesInJava8(List<Dish> dishes){
    return dishes.stream()
        .filter(d -> d.getCalories() < 400)
        .sorted(comparing(Dish::getCalories))
        .map(Dish::getName)
        .collect(toList());
}

若是須要多核並行處理,則只需調用 dishes.parallelStream() 便可ide

在 Java8 以前,程序員須要經過 2次遍歷 + 一次集合排序才能完成的工做,Java8 只須要一個鏈式調用就能夠解決。這就是 stream 的強大之處函數

img

認識流

流是什麼

流是 Java API 的新成員,容許程序員以聲明式的方式處理集合數據,而且支持鏈式調用、支持並行處理。用流處理的集合數據高效且易讀。優化

流與集合的異同

  1. 集合的主要功能是以必定的時間和空間複雜度存儲和訪問元素,而流主要是用於元素計算
  2. 集合中的元素能夠隨意添加和刪除,而流不能添加和刪除元素
  3. 流的元素是按需計算的,只有當用到時他纔會參與計算,而集合中的元素必須提早全都準備好
  4. 流只能遍歷一次,下面的代碼會報錯 java.lang.IllegalStateException: stream has already been operated upon or closed 流已經被消費掉
List<String> names = Arrays.asList("Java8", "Lambdas", "In", "Action");
Stream<String> s = names.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);
  1. 集合採用外部迭代,流採用內部迭代。內部迭代意味着 Java 能夠替你選擇更優的迭代策略和並行處理。而外部迭代若是程序員想着作個更有的迭代/採用並行就至關於「下次必定」😅了

流操做分類

對流的操做能夠分爲兩類,能夠繼續執行下一個流操做的稱爲中間操做(方法的返回值是 Stream),關閉流的操做稱爲終止操做。code

中間操做

除非流水線上執行終端操做,不然中間操做不會執行任何處理。流會對中間操做進行合併、短路等優化

終端操做

終端操做會從流的流水線生成結果,返回一個非 Stream 的任意類型值

使用流

篩選和切片

篩選

filter(Predicate<? super T> predicate) 方法能夠將流中知足某條件的元素篩選出來。該方法接收一個謂詞函數,返回流。好比要選出某個蘋果集合中紅色的蘋果

List<Apple> appleList = new ArrayList<>();
List<Apple> redAppleList = appleList.stream().filter(a -> "red".equals(a.getColor())).collect(Collectors.toList());

去重

distinct() 方法會根據元素的 hashCode() 和 equals() 方法對流中元素進行去重操做

截斷

limit(n) 方法會返回流的前 n 個元素,對於有序集合List,流會按照添加順序返回前 n 個元素,而無序集合則不會

跳過

skip(n) 方法會跳過流的前 n 個元素,能夠經過 skip(m).limit(n) 返回列表中第 m - (m+n) 區間的元素,相似與 mysql 中的 limit m,n

映射

對流中的每一個元素應用函數

map(Function<? super T, ? extends R> mapper) 方法。該方法接收一個 Function 函數,對流中的每個元素使用。而後能夠返回任意類型的對象。有了該方法,就能夠結合 Lambda 表達式對集合中的元素使用函數進行各類轉換

流的扁平化

flatMap() 能夠將流操做中多個流合併成一個流的多個元素。舉個例子:集合 words 有兩個單詞,如今想得到[H, e, l, o, W, r, d] 在 split 方法執行完畢後,返回的是 Stream(String[]) 對象,而此時若是執行 map 方法,返回的就是多個流的集合(這個例子中就是兩個 Stream(String)),這時是沒法繼續接下來的 distinct 操做的,所以須要 flatMap 將兩個 Stream 扁平化成一個 Stream,而後進行操做

List<String> words = Arrays.asList("Hello", "World");

List<String> charList = words.stream().map(word -> word.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());

該方法的方法聲明 flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 中能夠看出,他所使用的函數式接口 Function 第二個泛型 R 必須是 Stream 流。即函數式接口的抽象方法返回值必須是 Stream 流及其子類對象。

查找和匹配

檢查謂詞是否至少匹配一個元素

anyMatch 方法能夠回答「流中是否存在至少一個複合謂詞條件的元素」返回 boolean 類型的值,所以是一個終端操做,例如

List<Integer> num = Arrays.asList(1, 2, 3, 4, 5, 6);

if (num.stream().anyMatch(n -> n % 3 == 0)) {
    System.out.println("集合中有元素是3的整數倍");
}

控制檯會輸出'集合中有元素是3的整數倍',由於集合中 三、6都是3的整數倍,符合謂詞的條件

檢查謂詞是否匹配全部元素

allMatch 方法和 anyMatch 方法原理相似,可是它僅當全部元素知足謂詞條件時,返回 true。

noneMatchallMatch 正好相反,僅當全部元素不知足謂詞條件時,返回 true

ps:和 && || 運算符相似,以上三個操做都用到了短路的思想來提升效率。

查找元素

findAny() 該方法返回當前流中的任意元素,能夠和其餘流操做結合使用,這裏須要注意 findAny() 返回的結果被 Optional 所包裹,Optional 是 Java8 爲優雅的避免 NPE 所採用的新 API,關於 Optional 的用法我會在下一篇博客和你們討論,敬請期待。這裏須要說明的就是 Optional.ifPresent(Consumer<? super T> consumer) 表示當 Optional 包裹的元素不爲空時,執行 consumer

num.stream().filter(n -> n > 2).findAny().ifPresent(System.out::println);

findFirst() 該方法返回當前流中的第一個元素,通常也和其餘流操做(例如 filter() 過濾)結合使用。與 findAny() 不一樣的是,他必定返回有序集合的第一個知足條件的元素。固然有得必有失,做爲代價,findFirst() 在並行處理時限制更多一些。

歸約

元素求和

reduce(T identity, BinaryOperator<T> accumulator); 方法接收兩個參數:identity 初始值,accumulator 對兩個數的操做。例如求集合中數字的和:

num.stream().reduce(0, (a, b) -> a + b) // 計算完成,返回 21

ps:Lambda 表達式 (a, b) -> a + b) 中 a 是上一輪執行完後的累計值,b 是本次循環流中的元素。經過累加就能夠計算出數字的和

最大值和最小值

reduce 方法不只能夠求和、求積。甚至能夠計算最大值、最小值。

num.stream().reduce(Integer::max);
num.stream().reduce(Integer::min);

總結

  1. 流是 Java API 的新成員,容許程序員以聲明式的方式處理集合數據,而且支持鏈式調用、支持並行處理。用流處理的集合數據高效且易讀。
  2. 流的API中能夠分爲兩大類,中間操做和終端操做,中間操做返回流對象,能夠鏈式調用,終端操做則返回非流對象。
  3. 流提供了不少方便的API,如篩選 filter、去重 distinct、截斷 limit、跳過 skip、函數轉換 map、扁平化 flatMap、判斷流中是否有任意元素符合要求 anyMatch、是否全部元素都符合要求 allMatch、是否全部元素都不符合要求 noneMatch、查找元素 findAny findFirst、累計式的計算元素 reduce

碼字不易,若是你以爲讀完之後有收穫,不妨點個推薦讓更多的人看到吧!

相關文章
相關標籤/搜索