Java 8 Stream

陳仁義發表於2019-03-01

流的操作型別分為兩種:

  • Intermediate:一個流可以後面跟隨零個或多個 intermediate 操作。其目的主要是開啟流,做出某種程度的資料對映/過濾,然後返回一個新的流,交給下一個操作使用。這類操作都是惰性化的(lazy),就是說,僅僅呼叫到這類方法,並沒有真正開始流的遍歷。
    map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

  • Terminal:一個流只能有一個 terminal 操作,當這個操作執行後,流就被使用“光”了,無法再被操作。所以這必定是流的最後一個操作。Terminal 操作的執行,才會真正開始流的遍歷,並且會生成一個結果,或者一個 side effect
    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

  • Short-circuiting:對一個無限及集合返回有限的資料
    anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

Filter

物件匹配過濾

方法 示意
filter 通過傳遞一個預期匹配的物件作為引數並返回一個包含所有匹配到的物件的流。
distinct 返回包含唯一元素的流(唯一性取決於元素相等的實現方式)。
limit 返回一個特定上限的流
skip 返回一個丟棄前n個元素的流
List expensiveInvoices
= invoices.stream()
.filter(inv -> inv.getAmount() > 10_000)
.limit(5)
.collect(Collectors.toList());
複製程式碼

Matching:

匹配是一個判斷是否匹配到給定屬性的普遍的資料處理模式,表示式最後返回boolean

方法 示意
allMacth 流物件每個都要匹配到
anyMatch 流物件有匹配到就中斷
noneMatch 流物件無匹配物件
boolean expensive =
invoices.stream()
.allMatch(inv -> inv.getAmount() > 1_000);
複製程式碼

Finding

:流介面還提供了像findFirst和findAny等從流中取出任意的元素。它們能與像filter方法相連線。findFirst和findAny都返回一個可選物件,在序列流中兩者都是返回第一個物件,在並行流中findAny返回第一個執行緒處理最快的資料

方法 示意
findFirst 返回第一個匹配到的物件
findAny 返回第一個匹配到的物件
Optional =
invoices.stream()
.filter(inv ->
inv.getCustomer() == Customer.ORACLE)
.findAny();
複製程式碼

Mapping

:流支援對映方法,傳遞一個函式物件作為方法,把流中的元素轉換成另一種型別。這種方法應用於單個元素,將其對映成新元素。

方法 示意
map 應用於單個元素,將其對映成新元素。
List ids
= invoices.stream()
.map(Invoice::getId)
.collect(Collectors.toList());
複製程式碼

Reducing

方法 示意
reduce(BinaryOperator) 一個函式主要用於累和,求最大值。
int product = numbers.stream().reduce(1, (a, b) -> a * b);
int max = numbers.stream().reduce(Integer.MIN_VALUE,
Integer::max);
複製程式碼

forEach,peek比較

forEach Terminal操作,不再返回stream物件,forEach迴圈體內不能修改自己包含的本地變數值,也不能用 break/return 之類的關鍵字提前結束迴圈。
peek可以對迴圈物件二次封裝並返回新的物件

sorted
它比陣列的排序更強之處在於你可以首先對 Stream 進行各類 map、filter、limit、skip 甚至 distinct 來減少元素數量後,再排序,這能幫助程式明顯縮短執行時間。

List<Person> persons = new ArrayList();
 for (int i = 1; i <= 5; i++) {
 Person person = new Person(i, "name" + i);
 persons.add(person);
 }
List<Person> personList2 = persons.stream().limit(2).sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
System.out.println(personList2);
複製程式碼

min/max/distinct(Terminal)

獲取最後計算的最小值,最大值和去重複

Collectors

目前為止你所瞭解的方法都是返回另一個流或者一個像boolean,int型別的值,或者返回一個可選物件。相比之下,collect方法是一個結束操作,它可以使流裡面的所有元素聚集到彙總結果。
傳遞給collect方法引數是一個java.util.stream.Collector型別的物件。Collector物件實際上定義了一個如何把流中的元素聚集到最終結果的方法。最開始,工廠方法Collectors.toList()被用來返回一個描述瞭如何把流轉變成一個List的Collector物件。後來Collectors類又內建了很多相似的collectors變數。例如,你可以用Collectors.groupingBy方法按消費者把發票分組

Map<Customer, List> customerToInvoices
= invoices.stream().collect(Collectors.group
ingBy(Invoice::getCustomer));
複製程式碼

groupingBy/partitioningBy

Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()).
 limit(100).
 collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
 Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
 System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
}
複製程式碼

在使用條件“年齡小於 18”進行分組後可以看到,不到 18 歲的未成年人是一組,成年人是另外一組。partitioningBy 其實是一種特殊的 groupingBy,它依照條件測試的是否兩種結果來構造返回的資料結構,get(true) 和 get(false) 能即為全部的元素物件。

map 處理的input和output一一對應

方法 示意
mapToInt 函式體返回值為int型別
mapToDouble 函式體返回值為Double型別。
mapToLong 函式體返回值為Long型別。

flatMap 處理的input和outPut為一對多關係

方法 示意
flatMapToInt 函式體返回值為int型別
flatMapDouble 函式體返回值為Double型別。
flatMapLong 函式體返回值為Long型別。
Stream<List<Integer>> inputStream = Stream.of(
 Arrays.asList(1),
 Arrays.asList(2, 3),
 Arrays.asList(4, 5, 6)
 );
Stream<Integer> outputStream = inputStream.
flatMap((childList) -> childList.stream());
複製程式碼

Parallel Streams

Stream API 支援方便的資料並行。換句話說,你可以明確地讓流管道以並行的方式執行而不用關心底層的具體實現。在這背後,Stream API使用了Fork/Join框架充分利用了你機器的多核架構。
你所需要做的無非是用parallelStream()方法替換stream()方法。
不是所有的都使用並行的方式,如下

  • parallel streams的內部實現依賴於將資料結構劃分成可以讓不同執行緒使用的難易程度。像陣列這種資料結構很容易劃分,而像連結串列或者檔案這種資料結構很難劃分
  • 越是計算流中單個元素花費的資源最高,應用並行越有意義。
  • 如果可能的話儘量用原始資料型別,這樣可以佔用更少的記憶體,也更快取命中率也更高。
  • 流中元素的資料量越大越好,因為並行的成本會分攤到所有元素,並行節省的時間相對會更多。當然,這也跟單個元素計算的成本相關。
  • 一般來說,核越多越好。

相關文章