Java 8 - Stream流骚操作解读
文章目錄
- 分類
- 中間操作
- 終端操作
- 使用Stream流
- 篩選和切片
- 用謂詞篩選 filter
- 篩選各異的元素 distinct
- 截短流 limit
- 跳過元素 skip
- 映射
- 對流中每一個元素應用函數 map
- 流的扁平化 flatMap
- 嘗試使用map 和 Arrays.stream() 【未解決】
- 使用flatMap 【解決】
- 查找和匹配
- 檢查謂詞中是否至少匹配一個元素 anyMatch
- 檢查謂詞中是否匹配所有元素 allMatch
- 檢查謂詞中都不匹配所有元素 noneMatch
- 什么是短路求值
- 查找元素 findAny
- 查找第一個元素 findFirst
- 何時使用 findFirst 和 findAny
分類
java.util.stream.Stream 中的 Stream 接口定義了許多操作。
我們來看個例子
可以分為兩大類操作
-
filter 、 map 和 limit 可以連成一條流水線
-
collect 觸發流水線執行并關閉它
可以連接起來的流操作稱為中間操作,關閉流的操作稱為終端操作
中間操作
諸如 filter 或 sorted 等中間操作會返回另一個流。這讓多個操作可以連接起來形成一個查詢。重要的是,除非流水線上觸發一個終端操作,否則中間操作不會執行任何處理 。
這是因為中間操作一般都可以合并起來,在終端操作時一次性全部處理。
為了搞清楚流水線中到底發生了什么,我們把代碼改一改,讓每個Lambda都打印出當前處理的數據
/*** 需求: 輸出小于400的Dish的名字 , 按照卡路里從第到高輸出* @param dishList* @return*/public static List<String> getDiskNamesByStream2(List<Dish> dishList) {return dishList.stream().filter(dish -> {System.out.println("filtering:" + dish.getName());return dish.getCalories() <400 ;}).map(dish -> {System.out.println("mapping:" + dish.getName());return dish.getName();}).limit(3).collect(Collectors.toList());}僅限于自己學習的時候這么寫哈
輸出
filtering:pork mapping:pork filtering:beef mapping:beef filtering:chicken mapping:chicken [pork, beef, chicken]這里面有好幾種優化利用了流的延遲性質。
- 第一,盡管很高于300卡路里的數據,但只選出了前三個。 因為 limit 操作和一種稱為短路的技巧
- 第二,盡管 filter 和 map 是兩個獨立的操作,但它們合并到同一次遍歷中了(我們把這種技術叫作循環合并)
終端操作
終端操作會從流的流水線生成結果。其結果是任何不是流的值,比如 List 、 Integer ,甚至 void 。例如,在下面的流水線中, forEach 是一個返回 void 的終端操作,它會對源中的每道菜應用一個Lambda。把 System.out.println 傳遞給 forEach ,并要求它打印出由 menu 生成的流中的每一個 Dish
dishList.stream().forEach(System.out::println);使用Stream流
流的使用一般包括三件事:
- 一個數據源(如集合)來執行一個查詢;
- 一個中間操作,形成一條流的流水線;
- 一個終端操作,執行流水線,并能生成結果
流的流水線背后的理念類似于構建器模式。 在構建器模式中有一個調用鏈用來設置一套配置(對流來說這就是一個中間操作鏈),接著是調用 built 方法(對流來說就是終端操作)。
列一下之前的代碼中我們用到的流操作,當然了不止這些
【中間操作】
【終端操作】
還有很多模式,過濾、切片、查找、匹配、映射和歸約可以用來表達復雜的數據處理查詢。
來看看其他的,當然了不全哈
篩選和切片
如何選擇流中的元素?
用謂詞篩選,篩選出各不相同的元素,忽略流中的頭幾個元素,或將流截短至指定長度.
用謂詞篩選 filter
Streams 接口支持 filter 方法 ,該操作會接受一個謂詞(一個返回boolean 的函數)作為參數,并返回一個包括所有符合謂詞的元素的流.
需求: 篩選出所有素菜
/*** 需求: 輸出所有的素菜* @param dishList* @return*/public static List<Dish> getVegetarianByStream(List<Dish> dishList){return dishList.stream().filter(Dish::isVegetarian).collect(Collectors.toList());}篩選各異的元素 distinct
流還支持一個叫作 distinct 的方法,它會返回一個元素各異(根據流所生成元素的hashCode 和 equals 方法實現)的流。
需求: 給定一組數據,篩選出列表中所有的偶數,并確保沒有重復
public static void testDistinct(){Arrays.asList(1,2,1,3,3,2,4).stream().filter(i -> i%2 ==0).distinct().forEach(System.out::println);}截短流 limit
流支持 limit(n) 方法,該方法會返回一個不超過給定長度的流。所需的長度作為參數傳遞給 limit 。如果流是有序的,則最多會返回前 n 個元素。
需求: 選出熱量超過300卡路里的頭三道菜
/*** 選出熱量超過300卡路里的頭三道菜* @param dishes* @return*/public static List<String> getTop3HighCa(List<Dish> dishes) {return dishes.stream().filter(d -> d.getCalories() > 300).limit(3) .collect(Collectors.toList());}展示了 filter 和 limit 的組合。可以看到,該方法只選出了符合謂詞的頭三個元素,然后就立即返回了結果。
請注意 limit 也可以用在無序流上,比如源是一個 Set 。這種情況下, limit 的結果不會以任何順序排列。
跳過元素 skip
流還支持 skip(n) 方法,返回一個扔掉了前 n 個元素的流。如果流中元素不足 n 個,則返回一 個空流。請注意, limit(n) 和 skip(n) 是互補的
需求: 跳過超過300卡路里的頭兩道菜,并返回剩下的
/*** 需求: 跳過超過300卡路里的頭兩道菜,并返回剩下的* @param dishes* @return*/public static List<Dish> skipTop2Over300Carl(List<Dish> dishes) {return dishes.stream().filter(d->d.getCalories()>300).skip(2) .collect(Collectors.toList());}映射
一個非常常見的數據處理套路就是從某些對象中選擇信息。比如在SQL里,你可以從表中選擇一列。Stream API也通過 map 和 flatMap 方法提供了類似的工具。
對流中每一個元素應用函數 map
流支持 map 方法,它會接受一個函數作為參數。這個函數會被應用到每個元素上,并將其映射成一個新的元素(使用映射一詞,是因為它和轉換類似,但其中的細微差別在于它是“創建一個新版本”而不是去“修改”)。
舉個例子 :
public static List<String> getMenu(List<Dish> dishes) {return dishes.stream().map(Dish::getName).collect(Collectors.toList());}功能: 把方法引用 Dish::getName 傳給了 map 方法來提取流中菜肴的名稱。
因為 getName 方法返回一個 String ,所以 map 方法輸出的流的類型就是 Stream<String>
【再來看個例子 】
給定一個單詞列表,想要返回另一個列表,顯示每個單詞中有幾個字母。
怎么做呢?你需要對列表中的每個元素應用一個函數。
這聽起來正好該用 map 方法去做!應用的函數應該接受一個單詞,并返回其長度。你可以像下面這樣,給 map 傳遞一個方法引用 String::length 來解決這個問題:
/*** 給定一個單詞列表,想要返回另一個列表,顯示每個單詞中有幾個字母。* @return*/public static List mapping(){List<String> list = Arrays.asList("abc","pear","child","artisan");return list.stream().map(d->d.length()).collect(Collectors.toList());}使用方法引用優化
public static List mapping(){List<String> list = Arrays.asList("abc","pear","child","artisan");return list.stream().map(String::length).collect(Collectors.toList());}那再看個
/*** 需求: 要找出每道菜的名稱有多長* @param dishes* @return*/public static List<Integer> getMenuLength(List<Dish> dishes) {return dishes.stream().map(Dish::getName).map(String::length).collect(Collectors.toList());}流的扁平化 flatMap
我們已經看到如何使用 map 方法返回列表中每個單詞的長度了。
讓我們擴展一下:對于一張單詞表 , 如何返回一張列表 , 列出里面各不相同的字符呢?
怎么實現呢?
是不是可以把每個單詞映射成一張字符表,然后調用 distinct 來過濾重復字符, 秒啊
public static List<String[]> test() {List<String> list = Arrays.asList("hello","world");return list.stream().map(t -> t.split(" ")).distinct().collect(Collectors.toList()) ;}(⊙o⊙)… ,不對。。。。
這個方法的問題在于,傳遞給 map 方法的Lambda為每個單詞返回了一個 String[] ( String列表)。因此, map 返回的流實際上是 Stream<String[]> 類型的。 我們真正想要的是用Stream<String> 來表示一個字符流
那這么辦呢?
嘗試使用map 和 Arrays.stream() 【未解決】
可以用 flatMap 來解決這個問題!讓我們一步步看看怎么解決它。
/*** 需求: 對于一張單詞表 , 如何返回一張列表 , 列出里面各不相同的字符呢?** 錯誤的寫法* @return*/public static List<Stream<String>> test2() {Stream<String> stream = Arrays.stream(new String[]{"hello", "world"});return stream.map(t->t.split(" ")).map(Arrays::stream).distinct().collect(Collectors.toList());}當前的解決方案仍然搞不定!這是因為,你現在得到的是一個流的列表(更準確地說是Stream<String>)。的確,你先是把每個單詞轉換成一個字母數組,然后把每個數組變成了一個獨立的流。
使用flatMap 【解決】
/*** 需求: 對于一張單詞表 , 如何返回一張列表 , 列出里面各不相同的字符呢?* <p>* 完美** @return*/public static List<String> test3() {Stream<String> stream = Arrays.stream(new String[]{"hello", "world"});return stream.map(t -> t.split(" ")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());}
使用 flatMap 方法的效果是,各個數組并不是分別映射成一個流,而是映射成流的內容 。 所有使用 flatMap(Arrays::stream) 時生成的單個流都被合并起來,即扁平化為一個流
一言以蔽之, flatmap 方法讓你把一個流中的每個值都換成另一個流,然后把所有的流連接起來成為一個流。
查找和匹配
另一個常見的數據處理套路是看看數據集中的某些元素是否匹配一個給定的屬性。StreamAPI通過 allMatch 、 anyMatch 、 noneMatch 、 findFirst 和 findAny 方法提供了這樣的工具。
檢查謂詞中是否至少匹配一個元素 anyMatch
anyMatch 方法可以回答“流中是否有一個元素能匹配給定的謂詞”。
比如,你可以用它來看
/*** 需求: 是否包含素菜*/public static void isVe(List<Dish> dishes) {if (dishes.stream().anyMatch(Dish::isVegetarian)){System.out.println("you su cai ");}}anyMatch 方法返回一個 boolean ,因此是一個終端操作.
檢查謂詞中是否匹配所有元素 allMatch
allMatch 方法的工作原理和 anyMatch 類似,但它會看看流中的元素是否都能匹配給定的謂詞。
比如,你可以用它來看看所有熱量是否都低于1000卡路里
/*** 需求: 看看所有熱量是否都低于1000卡路里*/public static void isHea(List<Dish> dishes) {if (dishes.stream().allMatch(d->d.getCalories()<1000)){System.out.println("oj8k ");}}檢查謂詞中都不匹配所有元素 noneMatch
和 allMatch 相對的是 noneMatch 。它可以確保流中沒有任何元素與給定的謂詞匹配。
/*** 需求: 沒有任何一個卡路里超過1000*/public static void isHeass(List<Dish> dishes) {if (dishes.stream().noneMatch(d->d.getCalories()>=1000)){System.out.println("muyou ");}}anyMatch 、 allMatch 和 noneMatch 這三個操作都用到了我們所謂的短路,這就是大家熟悉的Java中 && 和 || 運算符短路在流中的版本
什么是短路求值
有些操作不需要處理整個流就能得到結果。例如,你需要對一個用 and 連起來的大布爾表達式進行求職,不管表達式有多長,只要有一個是false,那么就可以推斷出整個表達式是false, 無需計算整個表達式,這就是短路。
對于流而言,某些操作 (例如 allMatch 、 anyMatch 、 noneMatch 、 findFirst 和 findAny )不用處理整個流就能得到結果。只要找到一個元素,就可以有結果了
同樣的,limit也是一個短路操作。 它只需要創建一個給?大小的流,而用不著處理流中所有的元素。在碰到無限大小的流的時?,這種操作就有用了:它們可以把無限流變成有限流。
查找元素 findAny
findAny 方法將返回當前流中的任意元素。它可以與其他流操作結合使用
舉個例子:找到一道素菜。你可以結合使用 filter 和 findAny 方法來實現這個查詢
/*** 需求: 找到一道素菜 */public static Optional<Dish> randomVeDish(List<Dish> dishes) {return dishes.stream().filter(Dish::isVegetarian).findAny();}流水線將在后臺進行優化使其只需走一遍,并在利用短路找到結果時立即結束。不過慢著代碼里面的 Optional 是個什么玩意兒?
Optional<T> 類( java.util.Optional )是一個容器類,代表一個值存在或不存在。在上面的代碼中, findAny 可能什么元素都沒找到。Java 8的庫設計人員引入了 Optional<T> ,這樣就不用返回容易出問題的 null 了。
幾個常用的方法
- isPresent() 將在 Optional 包含值的時候返回 true , 否則返回 false 。
- ifPresent(Consumer<T> block) 會在值存在的時候執行給定的代碼塊。它讓你傳遞一個接收 T 類型參數,并返回 void 的Lambda表達式。
- T get() 會在值存在時返回值,否則拋出一個 NoSuchElement 異常。
- T orElse(T other) 會在值存在時返回值,否則返回一個默認值。
查找第一個元素 findFirst
有些流有一個出現順序(encounter order)來指定流中項目出現的邏輯順序(比如由 List 或排序好的數據列生成的流)。對于這種流,你可能想要找到第一個元素。為此有一個 findFirst方法,它的工作方式類似于 findany 。
例如,給定一個數字列表, 找出第一個平方能被3整除的數
/*** 需求: 給定一個數字列表, 找出第一個平方能被3整除的數*/public static Optional<Integer> xxxx() {List<Integer> list = Arrays.asList(1,2,3,4,5,6);return list.stream().map(x -> x *x).filter(i -> i % 3 == 0).findFirst();}何時使用 findFirst 和 findAny
什么會同時有 findFirst 和 findAny 呢?————–>并行。找到第一個元素在并行上限制更多。如果你不關心返回的元素是哪個, 使用 findAny ,因為它在使用并行流時限制較少。
總結
以上是生活随笔為你收集整理的Java 8 - Stream流骚操作解读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 8 - Lambda从兴趣盎
- 下一篇: Java 8 - Stream流骚操作解