最初の要素が述語と一致しなくなるまで(潜在的に無限)Stream
を制限するJava 8ストリーム操作はありますか?
Java 9では、以下の例のようにtakeWhile
を使用して、10未満のすべての数値を印刷できます。
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
Java 8にはそのような操作はないため、一般的な方法で実装する最良の方法は何ですか?
このような操作はpossibleでJava 8 Stream
である必要がありますが、必ずしも効率的に実行できるとは限りません。たとえば、このような操作を必ずしも並列化することはできません。要素を順番に見る必要があるためです。
APIはそれを行う簡単な方法を提供しませんが、おそらく最も簡単な方法は、Stream.iterator()
を取得し、Iterator
をラップして "take-while"実装を取得し、Spliterator
に戻ってからStream
に戻ることです。または-多分-Spliterator
をラップしますが、この実装ではもう分割できません。
以下は、takeWhile
に対するSpliterator
の未テストの実装です。
static <T> Spliterator<T> takeWhile(
Spliterator<T> splitr, Predicate<? super T> predicate) {
return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
boolean stillGoing = true;
@Override public boolean tryAdvance(Consumer<? super T> consumer) {
if (stillGoing) {
boolean hadNext = splitr.tryAdvance(elem -> {
if (predicate.test(elem)) {
consumer.accept(elem);
} else {
stillGoing = false;
}
});
return hadNext && stillGoing;
}
return false;
}
};
}
static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
}
オペレーションtakeWhile
およびdropWhile
がJDK 9に追加されました。サンプルコード
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
jDK 9でコンパイルして実行すると、期待どおりに動作します。
JDK 9がリリースされました。ここからダウンロードできます: http://jdk.Java.net/9/
allMatch()
は短絡関数であるため、処理を停止するために使用できます。主な欠点は、テストを2回実行する必要があることです。1回は処理する必要があるかどうかを確認し、もう1回は続行するかどうかを確認します。
IntStream
.iterate(1, n -> n + 1)
.peek(n->{if (n<10) System.out.println(n);})
.allMatch(n->n < 10);
@ StuartMarks answer へのフォローアップとして。私の StreamEx ライブラリには takeWhile
操作があり、現在のJDK-9実装と互換性があります。 JDK-9で実行する場合、JDK実装に委任するだけです(MethodHandle.invokeExact
を介して、これは非常に高速です)。 JDK-8で実行する場合、「ポリフィル」実装が使用されます。私のライブラリを使用すると、問題は次のように解決できます。
IntStreamEx.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
takeWhile
は、 protonpackライブラリ で提供される関数の1つです。
Stream<Integer> infiniteInts = Stream.iterate(0, i -> i + 1);
Stream<Integer> finiteInts = StreamUtils.takeWhile(infiniteInts, i -> i < 10);
assertThat(finiteInts.collect(Collectors.toList()),
hasSize(10));
Java8 + rxjava を使用できます。
import Java.util.stream.IntStream;
import rx.Observable;
// Example 1)
IntStream intStream = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
.takeWhile(n ->
{
System.out.println(n);
return n < 10;
}
).subscribe() ;
// Example 2
IntStream intStream = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
.takeWhile(n -> n < 10)
.forEach( n -> System.out.println(n));
更新:Java 9 Stream
に takeWhile メソッドが追加されました。
ハッキングやその他のソリューションの必要はありません。それを使ってください!
私はこれが大幅に改善されると確信しています:(誰かがスレッドセーフにする可能性があります)
Stream<Integer> stream = Stream.iterate(0, n -> n + 1);
TakeWhile.stream(stream, n -> n < 10000)
.forEach(n -> System.out.print((n == 0 ? "" + n : "," + n)));
class TakeWhile<T> implements Iterator<T> {
private final Iterator<T> iterator;
private final Predicate<T> predicate;
private volatile T next;
private volatile boolean keepGoing = true;
public TakeWhile(Stream<T> s, Predicate<T> p) {
this.iterator = s.iterator();
this.predicate = p;
}
@Override
public boolean hasNext() {
if (!keepGoing) {
return false;
}
if (next != null) {
return true;
}
if (iterator.hasNext()) {
next = iterator.next();
keepGoing = predicate.test(next);
if (!keepGoing) {
next = null;
}
}
return next != null;
}
@Override
public T next() {
if (next == null) {
if (!hasNext()) {
throw new NoSuchElementException("Sorry. Nothing for you.");
}
}
T temp = next;
next = null;
return temp;
}
public static <T> Stream<T> stream(Stream<T> s, Predicate<T> p) {
TakeWhile tw = new TakeWhile(s, p);
Spliterator split = Spliterators.spliterator(tw, Integer.MAX_VALUE, Spliterator.ORDERED);
return StreamSupport.stream(split, false);
}
}
実際、Java 8で追加のライブラリを使用せずに、またはJava 9を使用して、2つの方法があります。
コンソールで2から20までの数字を印刷したい場合、これを行うことができます:
IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).allMatch(i -> i < 20);
または
IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).anyMatch(i -> i >= 20);
出力は両方の場合です:
2
4
6
8
10
12
14
16
18
20
誰も言及していませんanyMatchこれがこの投稿の理由です。
これは、JDK 9 Java.util.stream.Stream.takeWhile(Predicate)からコピーされたソースです。 JDK 8で動作するためのわずかな違い。
static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> p) {
class Taking extends Spliterators.AbstractSpliterator<T> implements Consumer<T> {
private static final int CANCEL_CHECK_COUNT = 63;
private final Spliterator<T> s;
private int count;
private T t;
private final AtomicBoolean cancel = new AtomicBoolean();
private boolean takeOrDrop = true;
Taking(Spliterator<T> s) {
super(s.estimateSize(), s.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED));
this.s = s;
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
boolean test = true;
if (takeOrDrop && // If can take
(count != 0 || !cancel.get()) && // and if not cancelled
s.tryAdvance(this) && // and if advanced one element
(test = p.test(t))) { // and test on element passes
action.accept(t); // then accept element
return true;
} else {
// Taking is finished
takeOrDrop = false;
// Cancel all further traversal and splitting operations
// only if test of element failed (short-circuited)
if (!test)
cancel.set(true);
return false;
}
}
@Override
public Comparator<? super T> getComparator() {
return s.getComparator();
}
@Override
public void accept(T t) {
count = (count + 1) & CANCEL_CHECK_COUNT;
this.t = t;
}
@Override
public Spliterator<T> trySplit() {
return null;
}
}
return StreamSupport.stream(new Taking(stream.spliterator()), stream.isParallel()).onClose(stream::close);
}
これがintで行われたバージョンです-質問で尋ねられました。
使用法:
StreamUtil.takeWhile(IntStream.iterate(1, n -> n + 1), n -> n < 10);
StreamUtilのコードは次のとおりです。
import Java.util.PrimitiveIterator;
import Java.util.Spliterators;
import Java.util.function.IntConsumer;
import Java.util.function.IntPredicate;
import Java.util.stream.IntStream;
import Java.util.stream.StreamSupport;
public class StreamUtil
{
public static IntStream takeWhile(IntStream stream, IntPredicate predicate)
{
return StreamSupport.intStream(new PredicateIntSpliterator(stream, predicate), false);
}
private static class PredicateIntSpliterator extends Spliterators.AbstractIntSpliterator
{
private final PrimitiveIterator.OfInt iterator;
private final IntPredicate predicate;
public PredicateIntSpliterator(IntStream stream, IntPredicate predicate)
{
super(Long.MAX_VALUE, IMMUTABLE);
this.iterator = stream.iterator();
this.predicate = predicate;
}
@Override
public boolean tryAdvance(IntConsumer action)
{
if (iterator.hasNext()) {
int value = iterator.nextInt();
if (predicate.test(value)) {
action.accept(value);
return true;
}
}
return false;
}
}
}
ライブラリを取得する AbacusUtil に移動します。必要な正確なAPIなどを提供します。
IntStream.iterate(1, n -> n + 1).takeWhile(n -> n < 10).forEach(System.out::println);
宣言:私はAbacusUtilの開発者です。
実行されるレピティションの正確な量がわかっている場合は、次のことができます
IntStream
.iterate(1, n -> n + 1)
.limit(10)
.forEach(System.out::println);
私も同様の要件を抱えていました-Webサービスを呼び出し、失敗した場合は3回再試行します。これらの多くの試行の後でも失敗する場合は、電子メール通知を送信します。たくさんググリングした後、anyMatch()
が救世主になりました。私のサンプルコードは次のとおりです。次の例では、最初の反復自体でwebServiceCallメソッドがtrueを返す場合、anyMatch()
を呼び出したため、ストリームはそれ以上反復しません。これがあなたが探しているものだと思います。
import Java.util.stream.IntStream;
import io.netty.util.internal.ThreadLocalRandom;
class TrialStreamMatch {
public static void main(String[] args) {
if(!IntStream.range(1,3).anyMatch(integ -> webServiceCall(integ))){
//Code for sending email notifications
}
}
public static boolean webServiceCall(int i){
//For time being, I have written a code for generating boolean randomly
//This whole piece needs to be replaced by actual web-service client code
boolean bool = ThreadLocalRandom.current().nextBoolean();
System.out.println("Iteration index :: "+i+" bool :: "+bool);
//Return success status -- true or false
return bool;
}
IntStream.iterate(1, n -> n + 1)
.peek(System.out::println) //it will be executed 9 times
.filter(n->n>=9)
.findAny();
ピークの代わりに、mapToObjを使用して最終的なオブジェクトまたはメッセージを返すことができます
IntStream.iterate(1, n -> n + 1)
.mapToObj(n->{ //it will be executed 9 times
if(n<9)
return "";
return "Loop repeats " + n + " times";});
.filter(message->!message.isEmpty())
.findAny()
.ifPresent(System.out::println);
端末を短絡する操作を除いて、ストリームを中止することはできません。これにより、値に関係なく一部のストリーム値が未処理のままになります。ただし、ストリームでの操作を避けたい場合は、ストリームに変換とフィルターを追加できます。
import Java.util.Objects;
class ThingProcessor
{
static Thing returnNullOnCondition(Thing thing)
{ return( (*** is condition met ***)? null : thing); }
void processThings(Collection<Thing> thingsCollection)
{
thingsCollection.stream()
*** regular stream processing ***
.map(ThingProcessor::returnNullOnCondition)
.filter(Objects::nonNull)
*** continue stream processing ***
}
} // class ThingProcessor
これは、物事がある条件を満たしたときに物事のストリームをヌルに変換し、ヌルを除去します。副作用に満足したい場合は、何かに遭遇したときに条件値をtrueに設定することができます。そのため、値に関係なく、後続のものはすべて除外されます。しかし、そうでない場合でも、処理したくない値をストリームから除外することで、多くの(すべてではないにしても)処理を節約できます。