本文部分摘自 On Java 8
流概述
集合優化了物件的存盤,大多數情況下,我們將物件存盤在集合是為了處理他們,使用流可以幫助我們處理物件,無需迭代集合中的元素,即可直接提取和操作元素,并添加了很多便利的操作,例如查找、過濾、分組、排序等一系列操作,
流的一個核心好處是:它使得程式更加短小并且易于理解,當結合 Lambda 運算式和方法參考時,會讓人感覺自成一體,總而言之,流就是一種高效且易于使用的處理資料的方式,
觀察下面的例子:
public class Randoms {
public static void main(String[] args) {
new Random(47) // 創建 Random 物件,并給一個種子
.ints(5, 20) // 產生一個限定了邊界的隨機整數流
.distinct() // 使流中的整數不重復
.limit(7) // 取前7個元素
.sorted() // 排序
.forEach(System.out::println); // 根據傳遞給它的函式對流中每個物件執行操作
}
}
通過上面的示例,我們可以發現流有如下特點:
- 流本身不存盤元素,并且不會改變源物件,相反,它會回傳一個持有結果的新流
- 流可以在不使用賦值或可變資料的情況下對有狀態的系統建模
- 流是一種宣告式編程風格,它宣告想要做什么,而非指明如何做
- 流的迭代過稱為內部迭代,你看不到迭代程序,可讀性更強
- 流是懶加載的,它會等到需要時才執行
流創建
創建流的方式有很多,下面逐個介紹:
1. Stream.of()
通過 Stream.of() 可以很容易地將一組元素轉化為流
Stream.of(new Bubble(1), new Bubble(2), new Bubble(3)).forEach(System.out::println);
Stream.of("a", "b", "c", "d", "e", "f").forEach(System.out::print);
Stream.of(3.14159, 2.718, 1.618).forEach(System.out::println);
2. stream()
每個集合也可以通過呼叫 stream() 方法來產生一個流
List<Bubble> list = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3));
list.stream().forEach(System.out::print);
Set<String> set = new HashSet<>(Arrays.asList("a", "b", "c", "d", "e", "f"));
set.stream().forEach(System.out::print);
3. Stream.generate()
使用 Stream.generate() 搭配 Supplier<T> 生成 T 型別的流
Stream.generate(Math::random).limit(10).forEach(System.out::print);
4. Stream.iterate()
Stream.iterate() 產生的流的第一個元素是種子,然后把種子傳遞給方法,方法的運行結果被添加到流,并作為下次呼叫 iterate() 的第一個引數
Stream.iterate(0, n -> n + 1).limit(10).forEach(System.out::print)
使用 Stream.generate() 和 Stream.iterate() 生成的無限流一定要用 limit() 截斷
5. Stream.builder()
使用建造者模式創建一個 builder 物件,然后將創建流所需的多個資訊傳遞給它,最后 builder 物件執行創建流的操作
Stream.Builder<String> builder = Stream.builder();
builder.add("a");
builder.add("b");
...
builder.build(); // 創建流
// builder.add("c") // 呼叫 build() 方法后繼續添加元素會產生例外
6. Arrays.stream()
Arrays 類中有一個名為 stream() 的靜態方法用于把陣列轉換成流
Arrays.stream(new double[] {3.14159, 2.718, 1.618}).forEach(System.out::print);
Arrays.stream(new int[] {1, 3, 5}).forEach(System.out::print);
Arrays.stream(new long[] {11, 22, 44, 66}).forEach(System.out::print);
// 選擇一個子域
Arrays.stream(new int[] {1, 3, 5, 7, 15, 28, 37}, 3, 6).forEach(System.out::print);
最后一次 stream() 的呼叫有兩個額外的引數,第一個引數告訴 stream() 從陣列的哪個位置開始選擇元素,第二個引數告知在哪里停止
7. IntStream.range()
IntStream 類提供 range() 方法用于生成整型序列的流,撰寫回圈時,這個方法會更加便利
IntStream.range(10, 20).sum(); // 求得 10 - 20 的序列和
IntStream.range(10, 20).forEach(System.out::print); // 回圈輸出 10 - 20
8. 亂數流
Random 類被一組生成流的方式增強了,可以生成一組亂數流
Random rand = new Random(47);
// 產生一個隨機流
rand.ints().boxed();
// 控制上限和下限
rand.ints(10, 20).boxed();
// 控制流的大小
rand.ints(2).boxed();
// 控制流的大小和界限
rand.ints(3, 3, 9).boxed();
Random 類除了能生成基本型別 int,long,double 的流,使用 boxed() 操作會自動把基本型別包裝為對應的裝箱型別
9. 正則運算式
Java8 在 java.util.regex.Pattern 中新增了一個方法 splitAsStream(),這個方法可以根據傳入的公式將字符序列轉化為流
Pattern.compile("[.,?]+").splitAsStream("a,b,c,d,e").forEach(System.out::print);
中間操作
中間操作具體包括去重、過濾、映射等操作,作用于從流中獲取的每一個物件,并回傳一個新的流物件,
1. 跟蹤和除錯
peek() 操作的目的是幫助除錯,它允許你無修改地查看流中的元素
Stream.of("a b c d e".split(" ")).map(w -> w + " ").peek(System.out::print);
2. 流元素排序
sorted() 可以幫助我們實作對流元素的排序,如果不使用默認的自然排序,則需要傳入一個比較器,也可以把 Lambda 函式作為引數傳遞給 sorted()
Stream.of("a b c d e".split(" ")).sorted(Comparator.reverseOrder())
.map(w -> w + " ").peek(System.out::print);
3. 移除元素
distinct() 可用于消除流中的重復元素
new Random(47).ints(5, 20).distinct().limit(7).forEach(System.out::println);
filter(Predicate) 將元素傳遞給過濾函式,若結果為 true,則保留元素
// 檢測質數
Stream.iterate(2, n -> n + 1).filter(i -> i % 2 ==0)
.limit(10).forEach(System.out::print)
4. 應用函式到元素
map(Function) 將函式操作應用到輸入流的元素,并將回傳值傳遞到輸出流
Arrays.stream(new String[] {"12", "23", "34"}).map(s -> "[" + s + "]")
.forEach(System.out::print)
另外還有 mapToInt(ToIntFunction)、mapToLong(ToLongFunction)、mapToDouble(ToDoubleFunction),操作和 map(Function) 相似,只是結果流為各自對應的基本型別
如果在將函式應用到元素的程序中拋出了例外,此時會把原始元素放到輸出流
5. 組合流
使用 flatMap() 將產生流的函式應用在每個元素上,然后將產生每個流都扁平化為元素
Stream.of(1, 2, 3).flatMap(i -> Stream.of("hello" + i)).forEach(System.out::println);
另外還有 flatMapToInt(Function)、flatMapToLong(Function)、flatMapToDouble(Function),操作和 flatMap() 相似,只是結果元素為各自對應的基本型別
Optional 類
如果在一個空流中嘗試獲取元素,結果肯定是得到一個例外,我們希望可以得到友好的提示,而不是糊你一臉 NullPointException,Optional 的出現就是為了解決臭名昭著的空指標例外
一些標準流操作回傳 Optional 物件,因為它們不能保證預期結果一定存在,包括:
-
findFirst()回傳一個包含第一個元素的 Optional 物件,如果流為空則回傳 Optional.empty
-
findAny()回傳包含任意元素的 Optional 物件,如果流為空則回傳 Optional.empty
-
max()和min()回傳一個包含最大值或者最小值的 Optional 物件,如果流為空則回傳 Optional.empty
-
reduce(Function)將函式的回傳值包裝在 Optional 中
1. 便利函式
Optional 類本質上是一個容器物件,所謂容器是指:它可以保存型別 T 的值,也可以保存一個 null,此外,Optional 提供了許多有用的方法,可以幫助我們不用顯示地進行空值檢測:
-
ifPresent()是否有值存在,存在放回 true,否則回傳 false
-
ifPresent(Consumer)當值存在時呼叫 Consumer,否則什么也不做
-
orElse(otherObject)如果值存在則直接回傳,否則生成 otherObject
-
orElseGet(Supplier)如果值存在則直接回傳,否則使用 Supplier 函式生成一個可替代物件
-
orElseThrow(Supplier)如果值存在則直接回傳,否則使用 Supplier 函式生成一個例外
下面是對 Optional 的一個簡單應用
class OptionalBasics {
static void test(Optional<String> optString) {
if(optString.isPresent())
System.out.println(optString.get());
else
System.out.println("Nothing inside!");
}
public static void main(String[] args) {
test(Stream.of("Epithets").findFirst());
test(Stream.<String>empty().findFirst()); // 生成一個空流
}
}
2.創建 Optional
當我們需要在自己的代碼中加入 Optional 時,可以使用下面三個靜態方法:
-
empty()生成一個空 Optional
-
of(value)將一個非空值包裝到 Optional 里
-
ofNullable(value)針對一個可能為空的值,為空時自動生成 Optional.empty,否則將值包裝在 Optional 中
3. Optional 物件操作
當我們的流管道生成 Optional 物件,下面三個方法可以使得 Optional 能做更多后續操作:
-
filter(Predicate)對 Optional 中的內容應用 Predicate 并將結果回傳,如果 Optional 不滿足 Predicate,將 Optional 轉化為空 Optional ,如果 Optional 已經為空,則直接回傳空 Optional
-
map(Function)如果 Optional 不為空,應用 Function 于 Optional 中的內容,并回傳結果,否則直接回傳 Optional.empty
-
flatMap(Function)一般應用于已生成 Optional 的映射函式,所以
flatMap()不會像map()那樣將結果封裝在 Optional 中
終端操作
終端操作將獲取流的最終結果,至此我們無法再繼續往后傳遞流,可以說,終端操作總是我們在使用流時所做的最后一件事
1. 陣列
當我們需要得到陣列型別的資料以便于后續操作時,可以使用下述方法產生陣列:
-
toArray()將流轉換成適當型別的陣列
-
toArray(generetor)生成自定義型別的陣列
2. 回圈
常見的如 forEach(Consumer),另外還有 forEachOrdered(Consumer),保證按照原始流的順序操作,第二種形式僅在引入并行流時才有意義,所謂并行流是將流分割為多個,并在不同的處理器上分別執行,由于多處理器并行操作的原因,輸出的結果可能會不一樣,因此需要用到 forEachOrdered(Consumer)
3. 集合
在這里我們只是簡單介紹一下常見的 Collectors 示例,實際上它還有一些非常復雜的實作,大多數情況下,java.util.stream.Collectors 中預設的 Collector 就能滿足我們的需求
-
collect(Collector)使用 Collector 收集流元素到結果集合中
-
collect(Supplier, BiConsumer, BiConsumer)第一個引數創建一個新的結果集合,第二個引數將下一個元素收集到結果集合中,第三個引數用于將兩個結果集合合并起來
4. 組合
組合意味著將流中所有元素以某種方式組合為一個元素
-
reduce(BinaryOperator)使用 BinaryOperator 來組合所有流中的元素,因為流可能為空,其回傳值為 Optional
-
reduce(identity, BinaryOperator)功能同上,但是使用 identity 作為其組合的初始值,因此如果流為空,identity 就是結果
看一段代碼示例:
Stream.generate(Math::random).limit(10)
.reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1).ifPresent(System.out::println);
回傳的結果是 Optional 型別,Lambda 運算式中的第一個引數 fr0 是 reduce 中上一次呼叫的結果,而第二個引數 fr1 是從流傳遞過來的值
5. 匹配
allMatch(Predicate)
如果流的每個元素提供給 Predicate 都回傳 true ,結果回傳為 true,在第一個 false 時,則停止執行計算
anyMatch(Predicate)
如果流的任意一個元素提供給 Predicate 回傳 true ,結果回傳為 true,在第一個 true 是停止執行計算
noneMatch(Predicate)
如果流的每個元素提供給 Predicate 都回傳 false 時,結果回傳為 true,在第一個 true 時停止執行計算
6. 查找
findFirst()
回傳第一個流元素的 Optional,如果流為慷訓傳 Optional.empty
findAny(
回傳含有任意流元素的 Optional,如果流為慷訓傳 Optional.empty
7. 資訊
count()
流中的元素個數
max(Comparator)
根據所傳入的 Comparator 所決定的最大元素
min(Comparator)
根據所傳入的 Comparator 所決定的最小元素
8. 數字流資訊
average()
求取流元素平均值
max() 和 min()
數值流操作無需 Comparator
sum()
對所有流元素進行求和
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/215865.html
標籤:Java
上一篇:Spring Cloud 整合分布式鏈路追蹤系統Sleuth和ZipKin實戰,分析系統瓶頸
下一篇:元類
