快速掌握Java8 Stream函數語言程式設計技巧
函數語言程式設計優勢
- “函式第一位”,即函式可以出現在任何地方。
- 可以把函式作為引數傳遞給另一個函式,還可以將函式作為返回值。
- 讓程式碼的邏輯更清晰更優雅。
- 減少了可變數(Immutable Variable)的宣告,程式更為安全。
- 支援惰性計算。
Lambda語法三部分
- 一個括號內用逗號分隔的形式引數,引數是函式式接口裡面方法的引數
- 一個箭頭符號:->
- 方法體,可以是表示式和程式碼塊,方法體函式式接口裡面方法的實現,如果是程式碼塊,則必須用{}來包裹起來,且需要一個return 返回值,但有個例外,若函式式接口裡面方法返回值是void,則無需{}。例如:
- (parameters) -> expression 或者 (parameters) -> { statements; }
方法引用是lambda表示式的一個簡化寫法,所引用的方法其實是lambda表示式的方法體實現,語法也很簡單,左邊是容器(可以是類名,例項名),中間是“::”,右邊是相應的方法名。如下所示:
ObjectReference::methodName
一般方法的引用格式是:
- 如果是靜態方法,則是ClassName::methodName。如 Object ::equals
- 如果是例項方法,則是Instance::methodName。如Object obj=new Object();obj::equals;
- 建構函式.則是ClassName::new
Stream是什麼
Stream是Java8中新加入的api,更準確的說: Java 8 中的 Stream 是對集合(Collection)物件功能的增強,它專注於對集合物件進行各種非常便利、高效的聚合操作,或者大批量資料操作 。Stream API 藉助於同樣新出現的 Lambda 表示式,極大的提高程式設計效率和程式可讀性。
以前我們處理複雜的資料只能通過各種for迴圈,不僅不美觀,而且時間長了以後可能自己都看不太明白以前的程式碼了,但有Stream以後,通過filter,map,limit等等方法就可以使程式碼更加簡潔並且更加語義化。 Stream的效果就像上圖展示的它可以先把資料變成符合要求的樣子(map),吃掉不需要的東西(filter)然後得到需要的東西(collect)。
Stream操作分類
Stream上的所有操作分為兩類:中間操作和結束操作,中間操作只是一種標記,只有結束操作才會觸發實際計算。中間操作又可以分為無狀態的(Stateless)和有狀態的(Stateful),無狀態中間操作是指元素的處理不受前面元素的影響,而有狀態的中間操作必須等到所有元素處理之後才知道最終結果,比如排序是有狀態操作,在讀取所有元素之前並不能確定排序結果;結束操作又可以分為短路操作和非短路操作,短路操作是指不用處理全部元素就可以返回結果,比如找到第一個滿足條件的元素。之所以要進行如此精細的劃分,是因為底層對每一種情況的處理方式不同。
Stream API等價實現
求出字串集合中所有以字母A開頭字串的最大長度
int longest = 0; for(String str : strings){ if(str.startsWith("A")){// 1. filter(), 保留以A開頭的字串 int len = str.length();// 2. mapToInt(), 轉換成長度 longest = Math.max(len, longest);// 3. max(), 保留最長的長度 } }
int longest = strings.stream() .filter(str -> str.startsWith("A")) .mapToInt(str -> str.length()) //.mapToInt(String::length) .max();
Stream序列與並行
Stream可以分為序列與並行兩種,序列流和並行流差別就是單執行緒和多執行緒的執行。 default Stream stream() : 返回序列流 default Stream parallelStream() : 返回並行流 stream()和parallelStream()方法返回的都是java.util.stream.Stream<E>型別的物件,說明它們在功能的使用上是沒差別的。唯一的差別就是單執行緒和多執行緒的執行。
Stream效能總結
1.對於簡單操作,比如最簡單的遍歷,Stream序列API效能明顯差於顯示迭代,但並行的Stream API能夠發揮多核特性。
2.對於複雜操作,Stream序列API效能可以和手動實現的效果匹敵,在並行執行時Stream API效果遠超手動實現。
所以,如果出於效能考慮:
- 對於簡單操作推薦使用外部迭代手動實現。
- 對於複雜操作,推薦使用Stream API。
- 在多核情況下,推薦使用並行Stream API來發揮多核優勢。
- 單核情況下不建議使用並行Stream API。 如果出於程式碼簡潔性考慮,使用Stream API能夠寫出更短的程式碼。即使是從效能方面說,儘可能的使用Stream API也另外一個優勢,那就是隻要Java Stream類庫做了升級優化,程式碼不用做任何修改就能享受到升級帶來的好處。
參考 Java 8 Stream的效能到底如何?
Stream 來源
所有流計算都有一種共同的結構:它們具有一個流來源、0 或多箇中間操作,以及一個終止操作。流的元素可以是物件引用 (Stream<String>),也可以是原始整數 (IntStream)、長整型 (LongStream) 或雙精度 (DoubleStream)。
因為 Java 程式使用的大部分資料都已儲存在集合中,所以許多流計算使用集合作為它們的來源。JDK 中的 Collection 實現都已增強,可充當高效的流來源。但是,還存在其他可能的流來源,比如陣列、生成器函式或內建的工廠(比如數字範圍),而且可以編寫自定義的流介面卡,以便可以將任意資料來源充當流來源。如上圖中一些流生成方法。
Stream 操作
中間操作負責將一個流轉換為另一個流,中間操作包括 filter()(選擇與條件匹配的元素)、map()(根據函式來轉換元素)、distinct()(刪除重複)、limit()(在特定大小處截斷流)和 sorted()。一些操作(比如 mapToInt())獲取一種型別的流並返回一種不同型別的流。
中間操作始終是惰性的:呼叫中間操作只會設定流管道的下一個階段,不會啟動任何操作。重建操作可進一步劃分為無狀態 和有狀態 操作。無狀態操作(比如 filter() 或 map())可獨立處理每個元素,而有狀態操作(比如 sorted() 或 distinct())可合併以前看到的影響其他元素處理的元素狀態。
資料集的處理在執行終止操作時開始,比如縮減(sum() 或 max())、應用 (forEach()) 或搜尋 (findFirst()) 操作。終止操作會生成一個結果或副作用。執行終止操作時,會終止流管道,如果您想再次遍歷同一個資料集,可以設定一個新的流管道。如下給出了一些終止流操作。
Stream 流與集合比較
集合是一種資料結構,它的主要關注點是在記憶體中組織資料,而且集合會在一段時間內持久存在。 流的關注點是計算,而不是資料。流沒有為它們處理的元素提供儲存空間,而且流的生命週期更像一個時間點 — 呼叫終止操作。 不同於集合,流也可以是無限的(Stream.generate、Stream.iterate);相應地,一些操作(limit()、findFirst())是短路,而且可在無限流上執行有限的計算。 程式 = 資料結構 + 演算法,集合即資料結構,流操作相當於演算法。
資料形式:集合是一個記憶體中的資料結構,它包含資料結構中目前所有的值——集合中的每個元素都得先算出來才能新增到集合中。(你可以往集合里加東西或者刪東西,但是不管什麼時候,集合中的每個元素都是放在記憶體裡的,元素都得先算出來才能成為集合的一部分。) 相比之下,流則是在概念上固定的資料結構(你不能新增或刪除元素),其元素則是按需計算的。
迭代方式:使用Collection介面需要使用者去做迭代(比如用for-each),這稱為外部迭代。相反,Streams庫使用內部迭代——它幫你把迭代做了,還把得到的流值存在了某個地方,你只要給出一個函式說要幹什麼就可以了。Steams庫的內部迭代可以自動選擇一種適合你硬體的資料表示和並行實現。
Stream 基本使用
filter篩選(中間操作)
輸出:4,5
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().filter(i -> i > 3);
distinct去重(中間操作)
輸出:1,2,3,4,5
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().distinct();
limit限制(中間操作)
輸出:1,1,2
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().limit(3);
skip跳過(中間操作)
輸出:2,3,4,5
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5); Stream<Integer> stream = integerList.stream().skip(2);
map流對映(中間操作)
輸出:6, 7, 2, 6
List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action"); Stream<Integer> stream = stringList.stream().map(String::length);
flatMap流轉換(中間操作)
輸出:1, 2, 3, 4
將一個流中的每個值都轉換為另一個流
List<List<Integer>> lists = new ArrayList<List<Integer>>() {{ add(Arrays.asList(1, 2)); add(Arrays.asList(3, 4)); }}; Stream<Integer> stream = lists.stream().flatMap(List::stream); //Stream<Integer> stream = lists.stream().flatMap(list -> list.stream());
allMatch匹配所有(中間操作)
輸出:
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().allMatch(i -> i > 3)) { System.out.println("值都大於3"); }
noneMatch全部不匹配(中間操作)
輸出:
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().noneMatch(i -> i > 3)) { System.out.println("值都小於3"); }
anyMatch匹配其中一個(中間操作)
輸出:存在大於3的值
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().anyMatch(i -> i > 3)) { System.out.println("存在大於3的值"); }
findFirst查詢第一個(終端操作)
輸出:4
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();
findAny隨機查詢一個(終端操作)
輸出:存在大於3的值
和findFirst操作相比,並行流優勢更大
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().findAny(i -> i > 3)) { System.out.println("存在大於3的值"); }
Stream 常用統計
List<Integer> ints = Arrays.asList(1, 1, 2, 2, 3); //統計流中元素個數 ints.stream().count(); ints.stream().collect(Collectors.counting()); //獲取流中最小值 ints.stream().min(Integer::compareTo); ints.stream().collect(Collectors.minBy(Integer::compareTo)); //獲取流中最大值 ints.stream().max(Integer::compareTo); ints.stream().collect(Collectors.maxBy(Integer::compareTo)); //求和 ints.stream().mapToInt(Integer::intValue).sum(); ints.stream().collect(Collectors.summingInt(Integer::intValue)); ints.stream().reduce(0, Integer::sum); //平均值 ints.stream().collect(Collectors.averagingInt(Integer::intValue)); //通過summarizingInt同時求總和、平均值、最大值、最小值 ints.stream().collect(Collectors.summarizingInt(Integer::intValue));
Stream 終端操作(collect)
List<Integer> ints = Arrays.asList(1, 1, 2, 2, 3); //返回List ints.stream().collect(Collectors.toList()); //返回Set ints.stream().collect(Collectors.toSet()); //返回Map ints.stream().collect(Collectors.toMap(k -> k, v -> v, (v1, v2) -> v1)); //group分組 ints.stream().collect(Collectors.groupingBy(k -> k)); //partitioningBy分割槽 ints.stream().collect(Collectors.partitioningBy(k -> k % 2 == 0));
Stream參考文獻
- 深入理解Java Stream流水線
- Java 8中Stream API 的這些奇技淫巧!
- 使用Stream API優化程式碼
- java.util.stream 庫簡介