Java 8和無限流

1.概述

在本文中,我們將研究[java.util.Stream](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) API,我們將看到如何使用該構造對無限的數據/元素流進行操作。

根據將流構建為惰性的事實,可以確定對元素的無限序列進行操作的可能性。

這種懶惰是通過可以在流上執行的兩種類型的操作之間的分隔來實現的: intermediate操作和terminal操作。

2.中級和終端運營

所有Stream操作都分為intermediate操作和**terminal操作**,並結合在一起形成流管道。

流管道由一個源(例如Collection ,數組,生成器函數,I / O通道或無限序列生成器)組成;然後是零個或多個中間操作和一個終止操作。

2.1。 Intermediate業務

Intermediate調用某些terminal操作,則不會執行Intermediate操作。

它們組成了一個Stream執行流水線。可以通過以下方法將intermediate操作添加到Stream管道:

  • filter()
  • map()
  • flatMap()
  • distinct()
  • sorted()
  • peek()
  • limit()
  • skip()

所有Intermediate操作都是惰性的,因此直到實際需要處理結果時才執行它們。

基本上, intermediate操作返回一個新的流。執行中間操作實際上並不執行任何操作,而是創建一個新的流,該新流在遍歷時將包含與給定謂詞匹配的初始流的元素。

因此,在執行管道的terminal操作之前,不會開始遍歷Stream

這是非常重要的屬性,對於無限流特別重要-因為它允許我們創建僅在調用Terminal操作時才實際調用的流。

2.2。 Terminal操作

Terminal操作可能會遍歷流以產生結果或產生副作用。

執行終端操作後,流管線被視為已消耗,無法再使用。在幾乎所有情況下,終端操作人員都很渴望在返回之前完成對數據源的遍歷和對管道的處理。

對於無限流,終端操作的急切性很重要,因為在處理時,我們需要仔細考慮Stream是否受到例如limit()轉換的適當limit()Terminal操作為:

  • forEach()
  • forEachOrdered()
  • toArray()
  • reduce()
  • collect()
  • min()
  • max()
  • count()
  • anyMatch()
  • allMatch()
  • noneMatch()
  • findFirst()
  • findAny()

這些操作中的每一個將觸發所有中間操作的執行。

3.無限流

既然我們了解了這兩個概念( Intermediate操作和Terminal操作),我們就可以編寫一個利用Streams惰性的無限流。

假設我們要創建一個從零開始的無限元素流,該元素流將增加兩個。然後我們需要在調用終端操作之前限制該順序。

在執行作為終端操作collect() limit()方法之前使用limit()方法至關重要,否則我們的程序將無限期運行:

// given

 Stream<Integer> infiniteStream = Stream.iterate(0, i -> i + 2);



 // when

 List<Integer> collect = infiniteStream

 .limit(10)

 .collect(Collectors.toList());



 // then

 assertEquals(collect, Arrays.asList(0, 2, 4, 6, 8, 10, 12, 14, 16, 18));

我們使用iterate()方法創建了一個無限流。然後我們調用limit()轉換和collect()終端操作。然後在結果List,由於Stream.的惰性List,我們將具有無限序列的前10個元素Stream.

4.元素的自定義類型的無限流

假設我們要創建一個無限的隨機UUIDs流。

使用Stream API實現此目標的第一步是創建這些隨機值的Supplier

Supplier<UUID> randomUUIDSupplier = UUID::randomUUID;

當我們定義一個供應商時,我們可以使用generate()方法創建一個無限流:

Stream<UUID> infiniteStreamOfRandomUUID = Stream.generate(randomUUIDSupplier);

然後,我們可以從該流中獲取幾個元素。如果我們希望程序在有限的時間內完成,我們需要記住使用limit()方法:

List<UUID> randomInts = infiniteStreamOfRandomUUID

 .skip(10)

 .limit(10)

 .collect(Collectors.toList());

我們使用skip()轉換來丟棄前10個結果,並獲取後10個元素。我們可以通過將Supplier接口的函數傳遞給Stream上的generate()方法來創建任何自定義類型元素的無限流。

6. Do-While –流式傳輸方式

假設我們在代碼中有一個簡單的do..while循環:

int i = 0;

 while (i < 10) {

 System.out.println(i);

 i++;

 }

我們正在打印i計數器十倍。我們可以期望可以使用Stream API輕鬆編寫此類構造,理想情況下,我們在流上將具有doWhile()方法。

不幸的是,流上沒有這樣的方法,當我們想要實現類似於標準do-while循環的功能時,我們需要使用limit()方法:

Stream<Integer> integers = Stream

 .iterate(0, i -> i + 1);

 integers

 .limit(10)

 .forEach(System.out::println);

我們用更少的代碼實現了相同的功能,如命令式while循環,但是對limit()函數的調用doWhile()Stream對像上具有doWhile()方法那樣具有描述性。

5.結論

本文介紹瞭如何使用Stream API創建無限流。將它們與limit() –轉換一起使用時,可以使某些情況變得更易於理解和實現。

所有這些示例的代碼支持都可以在GitHub項目中找到–這是一個Maven項目,因此應該很容易直接導入和運行。