java8實戰四:使用流--Stream
使用流
在本章中,你將會看到許多Stream API支援的許多操作.這些操作能讓你快速完成許多複雜的查詢.如篩選、切片、對映、查詢、匹配和歸約。
接下來,我們會看到一些特殊的流:數值流,來自檔案和陣列等多種來源的流,最後是無限流.
1 篩選和切片
在本節中,我們來看看如何選擇流中的元素:用謂詞篩選,篩選出各不相同的元素,忽略流
中的頭幾個元素,或將流截短至指定長度。
1.1 用謂詞篩選
filter()方法:
該操作會接受一個謂詞作為引數,並返回一個包含所有符合謂詞元素的流.
例如,你可以像下圖所示的這樣,篩選出所有素菜,建立一張素食選單:
@Test
public void test2() {
List<Dish> vegetarianMenu =
menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
System.out.println(vegetarianMenu);
}
1.2 篩選各異的元素
distinct()方法:
它會返回一個元素各異的流(根據流所生成元素的hashCode 和 equals 方法實現),
例如,以下程式碼會篩選出列表中所有的偶數,並確保沒有重複。
@Test
public void test3() {
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
}
1.3 截短流
limit()方法:
該方法會返回一個不超過給定長度的流。所需的長度作為引數傳遞
給 limit 。如果流是有序的,則最多會返回前 n 個元素。
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
你可以看到,該方法只選出了符合謂詞的頭三個元素,
然後就立即返回了結果。
請注意 limit 也可以用在無序流上,比如源是一個 Set 。這種情況下, limit 的結果不會以
任何順序排列
1.4 跳過元素
流還支援 skip(n) 方法,返回一個扔掉了前 n 個元素的流。如果流中元素不足 n 個,則返回一
個空流。請注意, limit(n) 和 skip(n) 是互補的!
@Test
public void test4() {
List<Dish> vegetarianMenu = menu.stream().filter(dish -> dish.getCalories() > 300)
.skip(2)
.collect(toList());
System.out.println(vegetarianMenu);
}
2 對映
一個非常常見的資料處理套路就是從某些物件中選擇資訊.比如在sql中,你可以選擇一列.
Stream API也通過 map()和flatMap()方法提供了類似的工具.
2.1 對流中每一個元素應用函式
map()
它會接受一個函式作為引數,這個函式會被應用到每個元素上.並將其對映成一個新的元素.
例如,下面的程式碼把方法引用 Dish::getName 傳給了 map 方法,
來提取流中菜餚的名稱:
@Test
public void test5() {
List<String> vegetarianMenu =
menu.stream()
.map(Dish::getName)
.collect(toList());
System.out.println(vegetarianMenu);
}
因為 getName 方法返回一個 String ,所以 map 方法輸出的流的型別就是 Stream 。
讓我們看一個稍微不同的例子來鞏固一下對 map 的理解。給定一個單詞列表,你想要返回另
一個列表,顯示每個單詞中有幾個字母。怎麼做呢?你需要對列表中的每個元素應用一個函式。
這聽起來正好該用 map 方法去做!
@Test
public void test6() {
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<Integer> integers = words.stream()
.map(String::length)
.collect(toList());
System.out.println(integers);
}
結果:
[6, 7, 2, 6]
現在讓我們回到提取菜名的例子。如果你要找出每道菜的名稱有多長,怎麼做?你可以像下
面這樣,再連結上一個 map :
@Test
public void test7() {
List<Integer> integers = menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(toList());
System.out.println(integers);
}
結果:
[4, 4, 7, 12, 4, 12, 5, 6, 6]
2.2 流的扁平化
你已經看到如何使用 map 方法返回列表中每個單詞的長度了。讓我們拓展一下:對於一張單
詞 表 , 如 何 返 回 一 張 列 表 , 列 出 裡 面 各 不 相 同 的 字 符 呢 ? 例 如 , 給 定 單 詞 列 表
[“Hello”,”World”] ,你想要返回列表 [“H”,”e”,”l”, “o”,”W”,”r”,”d”] 。
你可能首先想到這樣做
@Test
public void test9() {
List<String> words = Arrays.asList("Hello","World");
List<String[]> list = words.stream()
.map(word -> word.split(""))
.distinct()
.collect(toList());
System.out.println(list);
}
這個方法的問題在於,傳遞給 map 方法的Lambda為每個單詞返回了一個 String[] ( String
列表)。因此, map 返回的流實際上是 Stream
[[Ljava.lang.String;@1def03a, [Ljava.lang.String;@122cdd0]
使用 flatMap()
@Test
public void test11() {
List<String> words = Arrays.asList("Hello","World");
List<String> list = words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
System.out.println(list);
}
結果:
[H, e, l, o, W, r, d]
要想搞清楚 flatMap() 的效果,我們首先來看看 Arrays.stream() 方法
@Test
public void test10() {
List<String> words = Arrays.asList("Hello","World");
List<Stream<String>> list = words.stream()
.map(word -> word.split(""))
.map(Arrays::stream)
.distinct()
.collect(toList());
System.out.println(list);
}
結果:
[java.util.stream.ReferencePipeline$Head@1591d15, java.util.stream.ReferencePipeline$Head@1ae6ba4]
可以看出,Arrays.stream() 的方法可以接受一個數組併產生一個流.
而 flatMap(Arrays::stream) 的效果就是 把兩個流合併起來,即扁平化為一個流.
flatMap() 的引數是 Stream型別.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
3 查詢和匹配
另一個常見的資料處理套路是看看資料集中的某些元素是否匹配一個給定的屬性。Stream
API通過 allMatch 、 anyMatch 、 noneMatch 、 findFirst 和 findAny 方法提供了這樣的工具
3.1 檢查謂詞是否至少匹配一個元素
anyMatch()
@Test
public void test12() {
boolean b = menu.stream().anyMatch(Dish::isVegetarian);
if(b){
System.out.println("有素菜!");
}
}
anyMatch 方法可以回答“流中是否有一個元素能匹配給定的謂詞”。
anyMatch 方法返回一個 boolean ,因此是一個終端操作.
3.2 檢查謂詞是否匹配所有元素
allMatch()
@Test
public void test13() {
boolean b = menu.stream()
.allMatch(dish -> dish.getCalories() < 1000);
if (b) {
System.out.println("沒有高熱量事物,吃!");
}
}
noneMatch()
和 allMatch 相對的是 noneMatch 。它可以確保流中沒有任何元素與給定的謂詞匹配
@Test
public void test14() {
boolean b = menu.stream().noneMatch(dish -> {
return dish.getCalories() > 1000;
});
if (b) {
System.out.println("沒有高熱量事物,吃!");
}
}
短路求值
有些操作不需要處理整個流就能得到結果。例如,假設你需要對一個用 and 連起來的大布
爾表示式求值。不管表示式有多長,你只需找到一個表示式為 false ,就可以推斷整個表示式
將返回 false ,所以用不著計算整個表示式。這就是 短路。
對於流而言,某些操作(例如 allMatch 、 anyMatch 、 noneMatch 、 findFirst 和 findAny )
不用處理整個流就能得到結果。只要找到一個元素,就可以有結果了。同樣, limit 也是一個
短路操作:它只需要建立一個給定大小的流,而用不著處理流中所有的元素。在碰到無限大小
的流的時候,這種操作就有用了:它們可以把無限流變成有限流.
3.3 查詢元素
findAny()
@Test
public void test15() {
Optional<Dish> any = menu.stream()
.filter(dish -> dish.getCalories() == 450)
.findAny();
System.out.println(any);
any.ifPresent(dish -> {
System.out.println(dish.getCalories());
});
}
結果:
Optional[Dish{name='salmon', vegetarian=false, calories=450, type=FISH}]
450
可以看到 findAny() 返回一個 Optional 型別的值,
Optional 簡介
Optional 類( java.util.Optional )是一個容器類,代表一個值存在或不存在。在
上面的程式碼中, findAny 可能什麼元素都沒找到。Java 8的庫設計人員引入了 Optional ,這
樣就不用返回眾所周知容易出問題的 null 了。
Optional 裡面幾種可以迫使你顯式地檢查值是否存在或處理值不存在的情形的方法也不錯。
* isPresent() 將在 Optional 包含值的時候返回 true , 否則返回 false 。
* ifPresent(Consumer block) 會在值存在的時候執行給定的程式碼塊。我們在第3章
介紹了 Consumer 函式式介面;它讓你傳遞一個接收 T 型別引數,並返回 void 的Lambda
表示式。
* T get() 會在值存在時返回值,否則丟擲一個 NoSuchElement 異常。
* T orElse(T other) 會在值存在時返回值,否則返回一個預設值。
3.4 查詢第一個元素
FindFirst()
用來找到流中的第一個元素,它的工作方式類似於 findany
@Test
public void test16() {
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =
someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst(); // 9
System.out.println(firstSquareDivisibleByThree);
firstSquareDivisibleByThree.ifPresent(System.out::println);
}
結果:
Optional[9]
9
何時使用 findFirst 和 findAny????
**你可能會想,為什麼會同時有 findFirst 和 findAny 呢?答案是並行。找到第一個元素
在並行上限制更多。如果你不關心返回的元素是哪個,請使用 findAny ,因為它在使用並行流
時限制較少。**
4 歸約
到目前為止,你所見到的終端操作都返回一個boolean(allMatch之類),void(forEach),或Optional物件(findOny等).你也見過了可以用collect來將流中的元素合成一個List.
接下來,你將會看到如何把一個流中的元素組合起來,比如“計算選單中的總卡路里”或“選單中卡路里最高的菜是哪一個”.
此類查詢需要將流反覆組合起來,得到一個值比如Integer,這樣的查詢可被歸類為 歸約 操作.
用函數語言程式設計的術語來說,這稱為摺疊(fold),因為你可以講這個流看作一張長長的紙(你的流),反覆摺疊成一個小方塊,這就是摺疊操作的結果.
4.1 元素求和
reduce()
先看使用java8之前的 for-each 的做法
int sum = 0;
for (int x : numbers) {
sum += x;
}
numbers中的每一個元素都使用加法運算反覆迭代來得到結果.通過反覆使用加法,你把一個數字列表歸約成了一個數字.
要是還能把所有的數字相乘,而不必去複製貼上這段程式碼,豈不是很好?這正是 reduce 操
作的用武之地,它對這種重複應用的模式做了抽象。你可以像下面這樣對流中所有的元素求和
@Test
public void test17() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Integer reduce = numbers.stream().reduce(0, (a, b) -> a + b);
System.out.println(reduce);//15
}
reduce 接受兩個引數:
* 一個初始值,這裡是0
* 一個 BinaryOperator 來將兩個元素結合起來產生一個新值,這裡我們用的是
lambda (a, b) -> a + b
上面的操作效果我們可以理解為:把初始值和後面的函式每一次計算的結果相加求和.
@Test
public void test17() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Integer reduce = numbers.stream()
.reduce(1, (a,b)->a*b);
System.out.println(reduce);//120
}
這個操作效果我們可以理解為:把初始值和後面的函式每一次計算的結果相乘.
通過這兩個例子我們可以看到:reduce兩個引數的運算方式取決於後面Lambda的運算方式
Lambda反覆結合每個元素,直到流被歸約成一個值.
在java8中Integer類多了幾個靜態方法,
/**
* Adds two integers together as per the + operator.
*
* @param a the first operand
* @param b the second operand
* @return the sum of {@code a} and {@code b}
* @see java.util.function.BinaryOperator
* @since 1.8
*/
public static int sum(int a, int b) {
return a + b;
}
/**
* Returns the greater of two {@code int} values
* as if by calling {@link Math#max(int, int) Math.max}.
*
* @param a the first operand
* @param b the second operand
* @return the greater of {@code a} and {@code b}
* @see java.util.function.BinaryOperator
* @since 1.8
*/
public static int max(int a, int b) {
return Math.max(a, b);
}
/**
* Returns the smaller of two {@code int} values
* as if by calling {@link Math#min(int, int) Math.min}.
*
* @param a the first operand
* @param b the second operand
* @return the smaller of {@code a} and {@code b}
* @see java.util.function.BinaryOperator
* @since 1.8
*/
public static int min(int a, int b) {
return Math.min(a, b);
}
這不正好可以讓我們結合Lambda表示式嗎!(可能java8的設計者特意增加了這幾個操作適應Lambda程式設計需要),總之我們程式碼看起來可以更簡潔
@Test
public void test17() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Integer reduce = numbers.stream()
.reduce(0, Integer::sum);
System.out.println(reduce);
}
無初始值
reduce還有一個過載的載體,它不接受初始值,但是會返回一個 Optional 物件.
@Test
public void test18() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> optional = numbers.stream()
.reduce(Integer::sum);
System.out.println(optional);
}
為什麼它返回一個 Optional 呢?考慮到流中沒有值的情況,reduce操作便無法返回其和,因為沒有初始值.這就是為什麼結果被包裹在一個Optional物件裡,以表明結果可能不存在.
4.2 求最大值和最小值
@Test
public void test19() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
System.out.println("max="+max+",min="+min);
}
給定兩個元素能夠返回最大值的Lambda,redce會考慮流中的新值和下一個元素,併產生一個新的最大值,直到整個流消耗完.
歸約方法的優勢與並行化
相比於java8之前的迭代求和,我們使用reduce的好處在於,這裡的迭代被內部迭代抽象掉了,這讓內部迭代可以選擇並行執行reduce操作. 而迭代求和的例子要更新共享變數sum,這不是那麼容易並行化的.
如果你加入了同步,可能會發現執行緒競爭抵消掉了並行本應該帶來的效能提升.
這種計算的並行化需要另一種方法:將輸入分塊,再分塊求和,最後再合併起來.
這正式reduce所提供的,使用流來對所有的元素並行求和時,你的程式碼幾乎不用修改: stream() 換成了 parallelStream().
付諸實踐
我們先會議一下目前為止講到的所有流操作
public class TradeTest {
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
//(1) 找出2011年發生的所有交易,並按交易額排序(從低到高)。
@Test
public void test1(){
List<Transaction> list = transactions.stream()
.filter(transaction -> transaction.getYear() == 2011)
.sorted(Comparator.comparing(Transaction::getValue))
.collect(toList());
System.out.println(list);
}
//(2) 交易員都在哪些不同的城市工作過?
@Test
public void test2(){
List<String> list = transactions.stream()
.map(Transaction::getTrader)
.map(Trader::getCity)
.distinct()
.collect(toList());
System.out.println(list);
List<String> list1 = transactions.stream()
.map(transaction -> transaction.getTrader().getCity())
.distinct()
.collect(toList());
System.out.println(list1);
}
//(3) 查詢所有來自於劍橋的交易員,並按姓名排序。
@Test
public void test3(){
List<Trader> list = transactions.stream()
.map(Transaction::getTrader)
.filter(trader -> trader.getCity().equals("Cambridge"))
.sorted(Comparator.comparing(Trader::getName))
.collect(toList());
System.out.println(list);
List<Trader> list1 = transactions.stream()
.filter(transaction -> transaction.getTrader().getCity().equals("Cambridge"))
.map(Transaction::getTrader)
.sorted(Comparator.comparing(Trader::getName))
.collect(toList());
System.out.println(list1);
}
//(4) 返回所有交易員的姓名字串,按字母順序排序。
@Test
public void test4(){
List<String> list = transactions.stream()
.map(Transaction::getTrader)
.map(Trader::getName)
.sorted((s1, s2) -> s1.compareTo(s2))
//.distinct()
.collect(toList());
System.out.println(list);
List<String> list1 = transactions.stream()
.map(transaction -> transaction.getTrader().getName())
.sorted((s1, s2) -> s1.compareTo(s2))
.collect(toList());
System.out.println(list1);
}
//(5) 有沒有交易員是在米蘭工作的?
@Test
public void test5(){
boolean b = transactions.stream()
.map(Transaction::getTrader)
.anyMatch(trader -> trader.getCity().equals("Milan"));
System.out.println(" 有沒有交易員是在米蘭工作的?="+b);
boolean b1 = transactions.stream()
.anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan"));
System.out.println(" 有沒有交易員是在米蘭工作的?="+b1);
}
//(6) 列印生活在劍橋的交易員的所有交易額。
@Test
public void test6(){
List<Integer> list = transactions.stream()
.filter(transaction -> transaction.getTrader().getCity().equals("Cambridge"))
.map(Transaction::getValue)
.collect(toList());
System.out.println(list);
}
// (7) 所有交易中,最高的交易額是多少?
@Test
public void test7(){
Optional<Integer> reduce = transactions.stream()
.map(Transaction::getValue)
.reduce(Integer::max);
System.out.println(reduce);
}
//(8) 找到交易額最小的交易。
@Test
public void test8(){
Optional<Integer> reduce = transactions.stream()
.map(Transaction::getValue)
.reduce(Integer::min);
System.out.println(reduce);
}
//(9) 計算交易總額
@Test
public void test9(){
Integer reduce = transactions.stream()
.map(transaction -> transaction.getValue())
.reduce(0, Integer::sum);
System.out.println(reduce);
}
}
5 數值流
我們先看下這段程式碼有什麼問題
//(9) 計算交易總額
@Test
public void test9(){
Integer reduce = transactions.stream()
.map(transaction -> transaction.getValue())
.reduce(0, Integer::sum);
System.out.println(reduce);
}
這段程式碼的問題是,它有一個暗含的裝箱成本。每個 Integer 都必須拆箱成一個原始型別,
再進行求和
Java 8引入了三個原始型別特化流介面來解決這個問題:
5.1 原始型別流特化
IntStream,DoubleStream,LongStream 分別將流中的元素特化為 int ,double, long ,從而避免了暗含的裝箱成本.
每個介面都帶來了進行常用數值歸約的新方法,比如對數值流求和的 sum ,找到最大元素的 max 。
此外還有在必要時再把它們轉換回物件流的方法。
對映到數值流
將流轉化為特化版本的常用方法為 mapToInt(),mapToDouble(),mapToLong().
這些方法和前面說的 map 方法的工作方式一樣,只是它們返回的是一個特化流,而不是 Stream
@Test
public void test20() {
int sum = menu.stream() //返回一個Stream<Dish>
.mapToInt(Dish::getCalories) //返回一個IntStream
.sum();
System.out.println(sum);
}
請注意,如果流是空的, sum 預設返回 0 。 IntStream 還支援其他的方便方法,如max 、 min 、 average 等。
轉換回物件流 boxed()
同樣,一旦有了數值流,你可能會想把它轉換回非特化流。
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
預設值 OptionalInt
要找到 IntStream 中的最大元素,可以呼叫 max 方法,它會返回一個 OptionalInt :
OptionalInt optionalInt = menu.stream()
.mapToInt(Dish::getCalories)
.max();
現在,如果沒有最大值的話,你就可以顯式處理 OptionalInt 去定義一個預設值了:
@Test
public void test21() {
OptionalInt optionalInt = menu.stream()
.mapToInt(Dish::getCalories)
.max();
int max = optionalInt.orElse(0);
System.out.println(max);
}
數值範圍
和數字打交道時,有一個常用的東西就是數值範圍。比如,假設你想要生成1和100之間的所有數字.
java8引入了兩個可用於 IntStream 和LongStream的靜態方法,range 和 rangeclosed.
這兩個方法都是第一個引數接受起始值,第二個引數接受結束值,但range是不包含結束值的, rangeclosed包含結束值
@Test
public void test22() {
IntStream intStream = menu.stream()
.mapToInt(Dish::getCalories);
IntStream intStream1 = IntStream.range(1, 100)
.filter(n -> n % 2 == 0);
System.out.println(intStream1.count());
}
6 構建流
本節將介紹如何從值序列、陣列、檔案來建立流,甚至由生成函式來建立無限流!
6.1 由值建立流
Stream.of()
你可以通過靜態方法 Stream.of() 通過顯示值建立一個流,他可以接受任意數量的引數.
@Test
public void test23() {
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
}
6.2 由陣列建立流
Arrays.stream()
@Test
public void test25() {
int[] numbers = {2, 3, 5, 7, 11, 13};
IntStream intStream = Arrays.stream(numbers);
int sum = intStream.sum();
System.out.println(sum);
}
Arrays.stream() 有很多過載方法.
6.3 由檔案建立流
java中用於處理檔案等I/O操作的NIO API已經更新,以便利用Stream API.
java.nio.file.Files 中的很多靜態方法都會返回一個流。例如,一個很有用的方法是
Files.lines ,它會返回一個由指定檔案中的各行構成的字串流.
統計一個檔案中有多少各不相同的詞:
@Test
public void test26() {
try {
Stream<String> stream = Files.lines(Paths.get("data.txt"));
//Arrays.stream()把字串陣列轉成流
long count = stream.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
System.out.println(count);
} catch (IOException e) {
e.printStackTrace();
}
}
你可以使用Files.lines得到一個流,其中每個元素都是給定檔案中的一行.
然後你可以對每一行進行split方法拆分單詞,應該注意的是:你應該如何使用 flatMap()產生一個扁平的單詞流.而不是給每一行生成一個單詞流.
6.4 由函式生成流:建立無限流
Stream API 提供了兩個靜態方法來從函式生成流: Stream.iterate和Stream.generate.
這兩個操作可以建立所謂的無限流:不像從固定集合建立的流那樣有固定大小的流。由 iterate
和 generate 產生的流會用給定的函式按需建立值,因此可以無窮無盡地計算下去!一般來說,
應該使用 limit(n) 來對這種流加以限制,以避免列印無窮多個值.
迭代 iterate
@Test
public void test27() {
Stream.iterate(0,n->n+2)
.limit(10)
.forEach(System.out::println);
}
iterate 方法接受一個初始值,還有一個作用於每次產生新值上的Lambda( UnaryOperator 型別),
這裡,我們使用Lambda n -> n + 2 ,返回的是前一個元
素加上2。因此, iterate 方法生成了一個所有正偶數的流.
這種iterate基本上是順序的,因為結果取決於前一次應用,請注意,此操作將生成一個無限流——這個流沒有結尾,因為值是按需計算的,可以永遠計算下去。我們說這個流是無界的。
生成 generate
與 iterate 方法類似, generate 方法也可讓你按需生成一個無限流。但 generate 不是依次
對每個新生成的值應用函式的。它接受一個 Supplier 型別的Lambda提供新的值。我們先來
看一個簡單的用法:
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
小結:
以下是你應從本文中學到的關鍵概念
Streams API可以表達複雜的資料處理查詢。常用的流操作總結在表5-1中
你可以使用 filter ,distinct ,limit ,skip對流做篩選和切片.
你可以使用map 和 flstMap 提取或者轉換流中的元素
你可以使用 findFirst和findAny 查詢流中的元素
你可以使用anyMatch ,allMatch ,noneMatch 讓流匹配給定的謂詞
上面這些方法都利用了短路:找到結果就立即停止計算,沒有必要處理整個流.
你可以使用 reduce 方法將流中的所有元素進行迭代合併成一個結果,例如求和或者查詢最大元素
filter和map操作是無狀態的,他們並不儲存任何狀態.
reduce 操作要儲存狀態才能計算出一個值,
sorted和distinct也要儲存狀態,因為他們把流中所有元素快取起來才能返回一個新的流. 這種操作稱為有狀態操作.流有三種基本的原始型別特化: IntStream 、 DoubleStream 和 LongStream 。它們的操作也有相應的特化
流不僅可以從集合建立,也可從值、陣列、檔案以及 iterate 與 generate 等特定方法建立。