歡迎來到Java 8 Stream API教程。在過去幾篇Java 8的文章中,我們介紹了Java 8介面變更和函數介面和Lambda表達式。今天我們將介紹Java 8中引入的一個重要API – Java Stream。
Java 8 Stream
- Java 8 Stream
- 集合和Java Stream
- Java 8 Stream中的函數介面
- java.util.Optional
- java.util.Spliterator
- Java Stream 中間操作和終端操作
- Java Stream 短路操作
- Java Stream 示例
- Java 8 Stream API 的限制
Java Stream
在我們查看 Java Stream API 示例之前,讓我們看看為什麼需要它。假設我們想要迭代一個整數列表並找出所有大於 10 的整數的總和。在 Java 8 之前,做這件事的方法是:
private static int sumIterator(List<Integer> list) {
Iterator<Integer> it = list.iterator();
int sum = 0;
while (it.hasNext()) {
int num = it.next();
if (num > 10) {
sum += num;
}
}
return sum;
}
上面方法存在三個主要問題:
- 我們只想知道整數的總和,但我們也需要提供迭代的方式,這也被稱為外部迭代,因為客戶端程序處理迭代列表的算法。
- 該程序的性質是順序的,我們無法輕易地並行處理。
- 即使是一個簡單的任務,也需要大量的代碼。
為了克服上述所有缺點,Java 8引入了Stream API。我們可以使用Java Stream API來實現內部迭代,這更好,因為Java框架控制著迭代。內部迭代提供了多種功能,如順序和並行執行、基於給定條件的過濾、映射等。大多數Java 8 Stream API的方法參數都是函數接口,所以使用lambda表達式非常方便。讓我們看看如何使用Java Streams在一個單行語句中寫出上述邏輯。
private static int sumStream(List<Integer> list) {
return list.stream().filter(i -> i > 10).mapToInt(i -> i).sum();
}
請注意,上述程序利用了Java框架的迭代策略、過濾和映射方法,並提高了效率。首先,我們將研究Java 8 Stream API的核心概念,然後通過一些例子來理解最常用的方法。
集合和Java Stream
A collection is an in-memory data structure to hold values and before we start using collection, all the values should have been populated. Whereas a java Stream is a data structure that is computed on-demand. Java Stream doesn’t store data, it operates on the source data structure (collection and array) and produce pipelined data that we can use and perform specific operations. Such as we can create a stream from the list and filter it based on a condition. Java Stream operations use functional interfaces, that makes it a very good fit for functional programming using lambda expression. As you can see in the above example that using lambda expressions make our code readable and short. Java 8 Stream internal iteration principle helps in achieving lazy-seeking in some of the stream operations. For example filtering, mapping, or duplicate removal can be implemented lazily, allowing higher performance and scope for optimization. Java Streams are consumable, so there is no way to create a reference to stream for future usage. Since the data is on-demand, it’s not possible to reuse the same stream multiple times. Java 8 Stream support sequential as well as parallel processing, parallel processing can be very helpful in achieving high performance for large collections. All the Java Stream API interfaces and classes are in the java.util.stream
package. Since we can use primitive data types such as int, long in the collections using auto-boxing and these operations could take a lot of time, there are specific classes for primitive types – IntStream
, LongStream
and DoubleStream
.
Java 8 Stream中的功能性接口
Java 8 Stream API方法中常用的一些功能性接口包括:
- Function和BiFunction:Function表示接受一种类型参数并返回另一种类型参数的函数。
Function<T, R>
是通用形式,其中 T 是函数输入的类型,R 是函数结果的类型。为处理基本类型,有特定的Function接口 –ToIntFunction
、ToLongFunction
、ToDoubleFunction
、ToIntBiFunction
、ToLongBiFunction
、ToDoubleBiFunction
、LongToIntFunction
、LongToDoubleFunction
、IntToLongFunction
、IntToDoubleFunction
等。一些使用Function
或其基本类型的 Stream 方法包括:- <R> Stream<R> map(Function<? super T, ? extends R> mapper)
- IntStream mapToInt(ToIntFunction<? super T> mapper) – 同样适用于长整型和双精度型的基本类型流。
- IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper) – 同样适用于长整型和双精度型
- <A> A[] toArray(IntFunction<A[]> generator)
- <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
- Predicate 和 BiPredicate: 它表示對流元素進行測試的謬論。 這用於從java流中過濾元素。 就像
Function
一樣,還有專門針對 int、long 和 double 的原始特定接口。 一些使用了Predicate
或BiPredicate
特化版本的Stream方法包括:- Stream<T> filter(Predicate<? super T> predicate)
- boolean anyMatch(Predicate<? super T> predicate)
- boolean allMatch(Predicate<? super T> predicate)
- boolean noneMatch(Predicate<? super T> predicate)
- Consumer 和 BiConsumer:它代表接受單個輸入參數並且不返回結果的操作。它可用於對 Java 流的所有元素執行某些操作。一些使用了
Consumer
、BiConsumer
或其原始特化接口的 Java 8 Stream 方法包括:- Stream<T> peek(Consumer<? super T> action)
- void forEach(Consumer<? super T> action)
- void forEachOrdered(Consumer<? super T> action)
- Supplier:Supplier 代表一個操作,通過它我們可以在流中生成新的值。Stream 中接受
Supplier
參數的一些方法包括:- public static<T> Stream<T> generate(Supplier<T> s)
- <R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner)
java.util.Optional
Java Optional 是一個容器對象,可能包含也可能不包含非空值。如果存在值,isPresent()
將返回 true,而 get()
將返回該值。流終端操作返回 Optional 對象。其中一些方法包括:
- Optional<T> reduce(BinaryOperator<T> accumulator)
- Optional<T> min(Comparator<? super T> comparator)
- Optional<T> max(Comparator<? super T> comparator)
- Optional<T> findFirst()
- Optional<T> findAny()
java.util.Spliterator
為了支持 Java 8 Stream API 中的並行執行,使用了 Spliterator
接口。Spliterator trySplit
方法返回一個管理原始 Spliterator 的元素子集的新 Spliterator。
Java Stream 中間和終端操作
Java Stream API 運算,回傳新的 Stream 稱為中介操作。大多數情況下,這些操作具有懶惰性質,因此它們開始產生新的流元素並將其發送到下一個操作。中介操作永遠不是最終產生結果的操作。常用的中介操作包括filter
和map
。Java 8 Stream API 運算,返回結果或產生副作用。一旦對流執行了終端方法,它就會消耗流,在那之後我們就無法再使用流。終端操作是急切的,即它們在返回結果之前處理流中的所有元素。常用的終端方法包括forEach
、toArray
、min
、max
、findFirst
、anyMatch
、allMatch
等。您可以從返回類型識別終端方法,它們永遠不會返回 Stream。
Java Stream 短路操作
如果中介操作可能針對無限流生成有限流,則稱為短路操作。例如,limit()
和skip()
是兩個短路中介操作。如果終端操作可能在無限流中以有限時間終止,則稱為短路終端操作。例如anyMatch
、allMatch
、noneMatch
、findFirst
和findAny
都是短路終端操作。
Java Stream 範例
I have covered almost all the important parts of the Java 8 Stream API. It’s exciting to use this new API features and let’s see it in action with some java stream examples.
創建 Java Streams
有幾種方法可以從陣列和集合創建 Java Stream。讓我們通過簡單的例子來看看這些方法。
-
我們可以使用
Stream.of()
來從類似類型的數據創建流。例如,我們可以從一組 int 或 Integer 對象創建 Java Stream 整數流。Stream<Integer> stream = Stream.of(1,2,3,4);
-
我們可以使用
Stream.of()
與對象數組一起返回流。請注意,它不支持自動裝箱,因此我們無法傳遞原始類型數組。Stream<Integer> stream = Stream.of(new Integer[]{1,2,3,4}); //運行正常 Stream<Integer> stream1 = Stream.of(new int[]{1,2,3,4}); //編譯時錯誤,類型不匹配:無法從 Stream<int[]> 轉換為 Stream<Integer>
-
我們可以使用 Collection
stream()
方法來創建順序流,使用parallelStream()
方法來創建平行流。List<Integer> myList = new ArrayList<>(); for(int i=0; i<100; i++) myList.add(i); // 順序流 Stream<Integer> sequentialStream = myList.stream(); // 平行流 Stream<Integer> parallelStream = myList.parallelStream();
-
我們可以使用
Stream.generate()
和Stream.iterate()
方法來創建 Stream。Stream<String> stream1 = Stream.generate(() -> {return "abc";}); Stream<String> stream2 = Stream.iterate("abc", (i) -> i);
-
使用
Arrays.stream()
和String.chars()
方法。LongStream is = Arrays.stream(new long[]{1,2,3,4}); IntStream is2 = "abc".chars();
將 Java Stream 轉換為 Collection 或 Array
有幾種方法可以從 Java Stream 中獲取 Collection 或 Array。
-
我們可以使用Java Stream的
collect()
方法從流中獲取List、Map或Set。Stream<Integer> intStream = Stream.of(1,2,3,4); List<Integer> intList = intStream.collect(Collectors.toList()); System.out.println(intList); //印出 [1, 2, 3, 4] intStream = Stream.of(1,2,3,4); //流已關閉,因此我們需要重新創建 Map<Integer,Integer> intMap = intStream.collect(Collectors.toMap(i -> i, i -> i+10)); System.out.println(intMap); //印出 {1=11, 2=12, 3=13, 4=14}
-
我們可以使用流的
toArray()
方法從流中創建一個數組。Stream<Integer> intStream = Stream.of(1,2,3,4); Integer[] intArray = intStream.toArray(Integer[]::new); System.out.println(Arrays.toString(intArray)); //印出 [1, 2, 3, 4]
Java Stream中間操作
讓我們看一下常用的Java Stream中間操作的例子。
-
Stream filter() example: 我們可以使用 filter() 方法來測試流元素是否符合條件並生成過濾後的列表。
List<Integer> myList = new ArrayList<>(); for(int i=0; i<100; i++) myList.add(i); Stream<Integer> sequentialStream = myList.stream(); Stream<Integer> highNums = sequentialStream.filter(p -> p > 90); //filter numbers greater than 90 System.out.print("高於90的數字="); highNums.forEach(p -> System.out.print(p+" ")); //prints "高於90的數字=91 92 93 94 95 96 97 98 99 "
-
Stream map() example: 我們可以使用 map() 方法來對流應用函數。讓我們看看如何將大寫函數應用於字符串列表。
Stream<String> names = Stream.of("aBc", "d", "ef"); System.out.println(names.map(s -> { return s.toUpperCase(); }).collect(Collectors.toList())); //prints [ABC, D, EF]
-
Stream sorted() example: 我們可以使用 sorted() 通過傳遞 Comparator 參數來對流元素進行排序。
Stream<String> names2 = Stream.of("aBc", "d", "ef", "123456"); List<String> reverseSorted = names2.sorted(Comparator.reverseOrder()).collect(Collectors.toList()); System.out.println(reverseSorted); // [ef, d, aBc, 123456] Stream<String> names3 = Stream.of("aBc", "d", "ef", "123456"); List<String> naturalSorted = names3.sorted().collect(Collectors.toList()); System.out.println(naturalSorted); //[123456, aBc, d, ef]
-
Stream flatMap() example: 我們可以使用 flatMap() 將流從列表的流中創建出來。讓我們看一個簡單的例子來澄清這個疑問。
Stream<List<String>> namesOriginalList = Stream.of( Arrays.asList("Pankaj"), Arrays.asList("David", "Lisa"), Arrays.asList("Amit")); //flat the stream from List<String> to String stream Stream<String> flatStream = namesOriginalList .flatMap(strList -> strList.stream()); flatStream.forEach(System.out::println);
Java Stream 終端操作
讓我們來看一些 Java Stream 終端操作的例子。
-
Stream reduce() 範例:我們可以使用 reduce() 對流中的元素進行累加操作,使用一個可結合的累加函數,並返回一個 Optional。讓我們看一下如何使用它來將流中的整數相乘。
Stream<Integer> numbers = Stream.of(1,2,3,4,5); Optional<Integer> intOptional = numbers.reduce((i,j) -> {return i*j;}); if(intOptional.isPresent()) System.out.println("Multiplication = "+intOptional.get()); //120
-
Stream count() 範例:我們可以使用這個終端操作來計算流中的項目數量。
Stream<Integer> numbers1 = Stream.of(1,2,3,4,5); System.out.println("Number of elements in stream="+numbers1.count()); //5
-
Stream forEach() example: 這可用於遍歷流。我們可以用這個來取代迭代器。讓我們看看如何用它來打印流的所有元素。
Stream<Integer> numbers2 = Stream.of(1,2,3,4,5); numbers2.forEach(i -> System.out.print(i+",")); //1,2,3,4,5,
-
Stream match() examples: 讓我們看看 Stream API 中一些匹配方法的示例。
Stream<Integer> numbers3 = Stream.of(1,2,3,4,5); System.out.println("Stream contains 4? "+numbers3.anyMatch(i -> i==4)); //Stream contains 4? true Stream<Integer> numbers4 = Stream.of(1,2,3,4,5); System.out.println("Stream contains all elements less than 10? "+numbers4.allMatch(i -> i<10)); //Stream contains all elements less than 10? true Stream<Integer> numbers5 = Stream.of(1,2,3,4,5); System.out.println("Stream doesn't contain 10? "+numbers5.noneMatch(i -> i==10)); //Stream doesn't contain 10? true
-
Stream findFirst() 範例: 這是一個短路終端操作,讓我們看看如何使用它來從一個以D開頭的串流中找到第一個字串。
Stream<String> names4 = Stream.of("Pankaj","Amit","David", "Lisa"); Optional<String> firstNameWithD = names4.filter(i -> i.startsWith("D")).findFirst(); if(firstNameWithD.isPresent()){ System.out.println("以D開頭的第一個名字="+firstNameWithD.get()); //David }
Java 8 Stream API 的限制
Java 8 Stream API 帶來了很多在列表和數組上操作的新功能,但也有一些限制。
-
無狀態 lambda 運算式: 如果您使用並行流且 lambda 運算式是有狀態的,可能會導致隨機的回應。讓我們通過一個簡單的程序來看看。
StatefulParallelStream.java
package com.journaldev.java8.stream; import java.util.ArrayList; import java.util.Arrays; import.util.List; import java.util.stream.Stream; public class StatefulParallelStream { public static void main(String[] args) { List<Integer> ss = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); List<Integer> result = new ArrayList<Integer>(); Stream<Integer> stream = ss.parallelStream(); stream.map(s -> { synchronized (result) { if (result.size() < 10) { result.add(s); } } return s; }).forEach( e -> {}); System.out.println(result); } }
如果我們運行上面的程序,會得到不同的結果,因為它取決於流的迭代方式,並且我們對並行處理沒有定義任何順序。如果使用順序流,則不會出現此問題。
-
一旦流被消耗,就無法後續使用。正如您在上面的示例中看到的,每次我都在創建一個流。
-
Stream API 中有很多方法,最令人困惑的部分是方法的重載。這使得學習曲線變得冗長。
這就是 Java 8 Stream 示例教程的全部內容。我期待著使用這個功能,通過並行處理使代碼更易讀並提高性能。參考: Java Stream API 文檔
Source:
https://www.digitalocean.com/community/tutorials/java-8-stream