在這章中我們將學習Stream API,在JDK 8 中的一項新的特性。為了理解這一章的主題,你需要知道如何使用Lambda表達式和java.util.function里的預定義的函數式接口。
一個Stream 類似于一個管道,但它里面運輸的不是水和石油,而是把數據從源頭運輸到目的地。根據傳遞的方式,一個stream可以是并行和并發的。并行的stream運行在多核的CPU的機器上會很有用。
乍一看,一個stream就像是一個集合容器,但是,它不是一個數據結構用來存儲對象,它只是負責移動對象,所以,你不能把它想象成集合對象那樣往它里面添加數據。
使用stream的主要原因是它支持并行和并發的聚合操作。例如,你可以非常容易地從stream里面過濾,排序或映射元素。
Stream API的不同的類型在java.util.stream包中。其中Stream接口是這里面最常用的stream類型。 一個Stream可以傳遞任何類型的對象,同時也有幾個特殊化的Stream:IntStream, LongStream and DoubleStream。他們都來源于BaseStream。
下面的表格展示了一些在Stream接口中常見的方法:
| 方法 | 描述 |
| concat | 懶加載的方式連接兩個stream。返回一個新的stream,他的元素包括兩個stream的所有元素。第一個stream的元素后面緊跟著第二個stram的元素。 |
| count | 返回stream里面元素的個數。 |
| empty | 創建并返回一個空的stream。 |
| filter | 在stream所有的元素中根據給定的斷言接口返回一個新的stream。 |
| forEach | 給stream每個元素執行一個操作。 |
| limit | 從當前的stream中根據指定最大元素的個數返回一個新的stream。 |
| map | 返回包含了應用于stream的元素的給定的方法的的結果的stream。 |
| max | 根據比較器返回stream中最大的元素。 |
| min | 根據比較器返回stream中最小的元素。 |
| of | 返回一個已經給定了值的stream。 |
| reduce | 在stream上使用唯一ID和累加器執行遞減操作。 |
| sorted | 返回一個新的使用自然排序的stream。 |
| toArray | 返回一個包含stream所有元素的數組。 |
有些stream的方法執行中間過程的操作,有的執行最終的操作。中間過程的操作會把一個stream傳輸到另一個stream中。像filter,Map,sorted等這些方法。
執行最終操作的方法會產生結果或是其他的影響。例如,count,forEach就是執行的最終結果的操作。
中間過程的操作屬于懶加載的方式,他不會真正的執行,只有是執行最終結果的才會真正在源上開始計算。
創建和獲取一個Stream
你可以使用Stream中靜態的of方法來創建一個連續的stream。例如,下面的例子就是創建了一個包含三個Integer類型元素的stream。
Stream<Integer> stream = Stream.of(100, 200, 300);
或者,給of方法傳遞一個數組:
String[] names = {"Bart", "Lisa", "Maggie"}; Stream<String> stream = Stream.of(names);
現在java.util.Arrays 幫助類已經有了method方法用來轉換一個數組給stream。例如,你可以重寫上面的代碼,使用Arrays類創建一個stream。
String[] names = {"Bart", "Lisa", "Maggie"}; Stream<String> stream = Arrays.stream(names);
另外,在java.util.Collectiond接口中也有個了默認的stream和parallelStream方法分別用來返回一個順序的stream和并行的stream。簽名如下:
default java.util.stream.Stream<E> stream() default java.util.stream.Stream<E> parallelStream()
多虧了Collection接口中的這些方法,從List或Set中獲取stream簡直小菜一碟。
除此而外,在java.nio.file.Files類中提供了兩個返回Stream<Path>的方法:list和walk。list方法返回一個指定路徑的入口的泛型為Path的stream。walk方法遍歷了給定路徑下入口里所有的文件并作為stream返回。
Files 也包含了lines方法返回泛型為String的stream的所有行的文本。
看下面的例子。
import java.io.IOException;import java.nio.file.FileVisitOption;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.stream.Stream;public class ObtainStreamDemo { public static void main(String[] args) throws IOException { Path path = Paths.get("."); // use list method. Stream<Path> list = Files.list(path); list.forEach(System.out::PRintln); list.close(); System.out.println("==========================================="); // use walk method. Stream<Path> walk = Files.walk(path, FileVisitOption.FOLLOW_LINKS); walk.forEach(System.out::println); walk.close(); }}連接兩個stream
在Stream接口中提供了concat方法用來以懶加載的方式連接兩個stream。這個方法返回一個新的stream,它的元素是兩個stream的所有元素,并且第二個stream的元素接在第一個stream元素的后面。
看下面的例子。
import java.util.stream.Stream;public class StreamConcatDemo { public static void main(String[] args) { Stream<String> stream1 = Stream.of("January", "Christie"); Stream<String> stream2 = Stream.of("Okanagan", "Sydney", "Alpha"); Stream.concat(stream1, stream2).sorted().forEach(System.out::println); }}需要注意的是,此方法不會剔除重復的元素,如果有相同的元素,都一并連接在一個新的stream中。
過濾。
當你從stream中基于一定的條件過濾該stream并返回一個新的包含選定的元素的stream。你可以在Stream對象上調用filter方法,并傳遞一個predicate函數式接口,由它來決定哪些元素包含在新的stream中。
filter方法的簽名如下:
Stream<T> filter(java.util.function.Predicate<? super T> predicate)
下面的例子,從exapmle.txt中讀取文件,并過濾掉注釋行(已“#”開頭的)和空白行。
public class StreamFilterDemo1 { public static void main(String[] args) { Predicate<String> notCommentOrEmptyLine = (line) -> line.trim().length() > 0 && !line.trim().startsWith("#"); try (FileReader fr = new FileReader("example.txt"); BufferedReader br = new BufferedReader(fr)) { Stream<String> lines = br.lines(); lines.filter(notCommentOrEmptyLine) .forEach(System.out::println); } catch (IOException e) { e.printStackTrace(); } }}example.txt:
# Set path so it includes user's private bin if it existsif [ -d "$HOME/bin" ] ; then PATH="$HOME/bin:$PATH"fi
執行結果如下:if [ -d "$HOME/bin" ] ; then PATH="$HOME/bin:$PATH"fi
第二個例子是使用stream實現在你機器上的文件搜索。為了精確些,代碼只顯示在給定的目錄和子目錄下后綴名為java的文件。
import java.io.IOException;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.stream.Stream;public class StreamFilterDemo2 { public static void main(String[] args) { // find all java files in the parent directory and // all its subdirectories Path parent = Paths.get(".."); try { Stream<Path> list = Files.walk(parent); list.filter((Path p) -> p.toString().endsWith(".java")) .forEach(System.out::println); } catch (IOException ex) { ex.printStackTrace(); } }}StreamFilterDemo2 類開始從當前目錄的父目錄開始執行,它傳遞了Path給Files.walk方法去獲取泛型為Paths的stream,接著根據predicate接口只包含后綴名為.java的文件,并用forEach遍歷打印。
新聞熱點
疑難解答