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

雲棲號資訊:【點擊查看更多行業資訊
在這裏您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

首先給大家看一段代碼,讓大家直觀感受下 Java7 和 Java8 遍歷處理集合的不同
Dish 是一個菜餚對象,calories 屬性表示該菜品的卡路里值,name 則是菜品的名稱。我們需要過濾出卡路里小於400、然後根據卡路里值升序、接着拿到他們的名稱列表並返回

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;
}

Java8

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() 即可
在 Java8 之前,程序員需要通過 2次遍歷 + 一次集合排序才能完成的工作,Java8 只需要一個鏈式調用就可以解決。這就是 Stream 的強大之處

CA89E7F6_CDD1_4680_963B_C7395DBBF0C8

認識流

流是什麼

流是 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);

5.集合採用外部迭代,流採用內部迭代。內部迭代意味着 Java 可以替你選擇更優的迭代策略和並行處理。而外部迭代如果程序員想着做個更有的迭代/採用並行就相當於“下次一定”😅了

流操作分類

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

中間操作

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

終端操作

終端操作會從流的流水線生成結果,返回一個非 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,然後進行操作關注公衆號互聯網架構師,回覆關鍵字2T,獲取最新架構視頻

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的整數倍',因爲集合中 3、6都是3的整數倍,符合謂詞的條件。

檢查謂詞是否匹配所有元素
allMatch 方法和 anyMatch 方法原理類似,但是它僅當所有元素滿足謂詞條件時,返回 true。
noneMatch 與 allMatch 正好相反,僅當所有元素不滿足謂詞條件時,返回 true
ps:和 && || 運算符類似,以上三個操作都用到了短路的思想來提高效率。

查找元素
findAny() 該方法返回當前流中的任意元素,可以和其他流操作結合使用,這裏需要注意 findAny() 返回的結果被 Optional 所包裹,Optional 是 Java8 爲優雅的避免 NPE 所採用的新 API,這裏需要說明的就是 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 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

【雲棲號在線課堂】每天都有產品技術專家分享!
課程地址:https://yqh.aliyun.com/zhibo

立即加入社羣,與專家面對面,及時瞭解課程最新動態!
【雲棲號在線課堂 社羣】https://c.tb.cn/F3.Z8gvnK

原文發佈時間:2020-07-16
本文作者:互聯網架構師
本文來自:“互聯網架構師”,瞭解相關信息可以關注“互聯網架構師

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章