データがデータソースから追加されたJavaストリームのためのこの使用シナリオを想定します。データソースは、以下の例のような値のリスト、またはページ付けRESTのAPIとすることができます。それは現時点では、重要ではありません。
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
final List<Boolean> dataSource = List.of(true, true, true, false, false, false, false);
final AtomicInteger index = new AtomicInteger();
Stream
.generate(() -> {
boolean value = dataSource.get(index.getAndIncrement());
System.out.format("--> Executed expensive operation to retrieve data: %b\n", value);
return value;
})
.takeWhile(value -> value == true)
.forEach(data -> System.out.printf("--> Using: %b\n", data));
}
}
このコードを実行する場合は、あなたの出力は次のようになります
--> Executed expensive operation to retrieve data: true
--> Using: true
--> Executed expensive operation to retrieve data: true
--> Using: true
--> Executed expensive operation to retrieve data: true
--> Using: true
--> Executed expensive operation to retrieve data: false
あなたが最後の要素を見ることができるように評価されていること1はfalse
予想通り、ストリームに追加されませんでした。
今と仮定generate()
REST APIからのデータの方法をロードするページ。その場合、値は、true/false
ページ上の値であるN
ページがどうかを示すN + 1
、のようなものが存在するhas_more
フィールドを。今、私はAPIによって返された最後のページは、ストリームに追加することにしたいが、私はすでにこれ以上のページがあることを知っているので、空のページを読むために、他の高価な操作を実行する必要はありません。
JavaのストリームAPIを使用して、これを行うための最も慣用的な方法は何ですか?私は考えることができるすべての問題を回避するには、APIの呼び出しが実行されている必要があります。
更新
記載されているアプローチに加えて、ストリームの包括takeWhile()これを達成する別の醜い方法があります。
public static void main(String[] args) {
final List<Boolean> dataSource = List.of(true, true, true, false, false, false, false);
final AtomicInteger index = new AtomicInteger();
final AtomicBoolean hasMore = new AtomicBoolean(true);
Stream
.generate(() -> {
if (!hasMore.get()) {
return null;
}
boolean value = dataSource.get(index.getAndIncrement());
hasMore.set(value);
System.out.format("--> Executed expensive operation to retrieve data: %b\n", value);
return value;
})
.takeWhile(Objects::nonNull)
.forEach(data -> System.out.printf("--> Using: %b\n", data));
}
あなたはあなたの仕事のための間違ったツールを使用しています。あなたのコード例では、すでに顕著なように、Supplier
に渡されStream.generate
、それがページをフェッチするために必要とインデックスを維持するために偉大な長さを行かなければなりません。
何、問題を悪化させるものはつまりStream.generate
作成順不同ストリームを:
各要素が提供サプライヤーによって生成された無限シーケンシャル順不同ストリームを返します。これは、等、ランダム要素の一定のストリームを生成するのに適しているストリーム
あなたは、一定またはランダムな値や順序に依存しないだろう何かを返していません。
これは、上の重大な影響があるの意味をtakeWhile
:
そうでなければ戻り、このストリームが順序付けられていない場合には、与えられた述語と一致、このストリームから取られた要素のサブセットからなるストリーム。
あなたはそれについて考える場合、これは理にかなっています。述語によって拒否された少なくとも一つの要素がある場合、それがための任意の位置に遭遇することができ順不同の空のセットを含め、その前に遭遇した要素の任意のサブセットは、有効な接頭辞になるので、ストリーム。
しかし、そこにあるので、ノー「前」または順不同ストリームのために、発電機によって生成しても要素が「後」の後に 1が結果に含まれる可能性が拒否されました。
実際には、順次ストリームのためにこのような効果が発生する可能性は低いですが、それは事実変わりませんStream.generate(…) .takeWhile(…)
、あなたの仕事のために意味的に間違っているし。
あなたの例のコードから、私たちは、ストリームを作成するための番号と「のhasNext」状態を維持する必要がありますのでページは、自分の番号も「getNextを」メソッドが含まれていないと結論付けています。
以下のような設定例を想定すると、
class Page {
private String data;
private boolean hasNext;
public Page(String data, boolean hasNext) {
this.data = data;
this.hasNext = hasNext;
}
public String getData() {
return data;
}
public boolean hasNext() {
return hasNext;
}
}
private static String[] SAMPLE_PAGES = { "foo", "bar", "baz" };
public static Page getPage(int index) {
Objects.checkIndex(index, SAMPLE_PAGES.length);
return new Page(SAMPLE_PAGES[index], index + 1 < SAMPLE_PAGES.length);
}
あなたのような正しいストリームを実装することができます
Stream.iterate(Map.entry(0, getPage(0)), Objects::nonNull,
e -> e.getValue().hasNext()? Map.entry(e.getKey()+1, getPage(e.getKey()+1)): null)
.map(Map.Entry::getValue)
.forEach(page -> System.out.println(page.getData()));
戻り値は、シーケンシャルストリームが与えられたのhasNext述語を満たすことが条件、初期要素に与えられた次の関数の反復アプリケーションによって生成命じました。
ページは例えば、独自の番号を、知っていればもちろん、物事がはるかに容易になるだろう
Stream.iterate(getPage(0), Objects::nonNull,
p -> p.hasNext()? getPage(p.getPageNumber()+1): null)
.forEach(page -> System.out.println(page.getData()));
または次のページに、既存のページから取得するための方法、例えばがあった場合には
Stream.iterate(getPage(0), Objects::nonNull, p -> p.hasNext()? p.getNextPage(): null)
.forEach(page -> System.out.println(page.getData()));