Existe-t-il une opération de flux Java 8 qui limite un Stream
(potentiellement infini) jusqu'à ce que le premier élément ne corresponde pas avec un prédicat?
Dans Java 9, nous pouvons utiliser takeWhile
comme dans l'exemple ci-dessous pour imprimer tous les nombres inférieurs à 10.
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
Comme il n’existe pas cette opération dans Java 8, quelle est la meilleure façon de la mettre en œuvre de manière générale?
Une telle opération devrait être possible avec un Java 8 Stream
, mais cela ne peut pas nécessairement être effectué efficacement - par exemple, vous ne pouvez pas nécessairement paralléliser une telle opération, car il faut regarder les éléments dans l’ordre.
L’API n’offre pas un moyen facile de le faire, mais le moyen le plus simple est probablement de prendre Stream.iterator()
, d’envelopper la Iterator
pour avoir une implémentation "take-while", puis de revenir à un Spliterator
puis un Stream
. Ou - peut-être - encapsulez la Spliterator
, bien qu'elle ne puisse plus être scindée dans cette implémentation.
Voici une implémentation non testée de takeWhile
sur un 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);
}
Les opérations takeWhile
et dropWhile
ont été ajoutées à JDK 9. Votre exemple de code
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
se comportera exactement comme vous le souhaiteriez lorsqu'il sera compilé et exécuté sous JDK 9.
JDK 9 a été libéré. Il est disponible au téléchargement ici: http://jdk.Java.net/9/
allMatch()
est une fonction de court-circuit, vous pouvez donc l'utiliser pour arrêter le traitement. Le principal inconvénient est que vous devez faire votre test deux fois: une fois pour voir si vous devez le traiter et une autre fois pour savoir s'il faut continuer.
IntStream
.iterate(1, n -> n + 1)
.peek(n->{if (n<10) System.out.println(n);})
.allMatch(n->n < 10);
En guise de suivi de @ StuartMarks answer . Ma bibliothèque StreamEx possède l'opération takeWhile
compatible avec la mise en œuvre actuelle de JDK-9. Sous JDK-9, il ne fera que déléguer à l'implémentation JDK (via MethodHandle.invokeExact
, ce qui est très rapide). Lors de l'exécution sous JDK-8, l'implémentation "polyfill" sera utilisée. Donc, en utilisant ma bibliothèque, le problème peut être résolu comme ceci:
IntStreamEx.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
takeWhile
est l'une des fonctions fournies par bibliothèque de protonpack .
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));
Vous pouvez utiliser 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));
Mise à jour: Java 9 Stream
vient maintenant avec une méthode takeWhile .
Pas besoin de piratage ou d'autres solutions. Il suffit d'utiliser ça!
Je suis sûr que cela peut être grandement amélioré: (quelqu'un pourrait le rendre compatible avec les threads peut-être)
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);
}
}
En fait, il y a 2 façons de le faire dans Java 8 sans aucune bibliothèque supplémentaire ou en utilisant Java 9.
Si vous souhaitez imprimer des nombres de 2 à 20 sur la console, vous pouvez procéder comme suit:
IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).allMatch(i -> i < 20);
ou
IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).anyMatch(i -> i >= 20);
La sortie est dans les deux cas:
2
4
6
8
10
12
14
16
18
20
Personne n'a encore mentionné anyMatch. C'est la raison de ce post.
Il s'agit de la source copiée à partir du JDK 9 Java.util.stream.Stream.takeWhile (Predicate). Une petite différence pour pouvoir travailler avec 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);
}
Voici une version réalisée sur ints - comme demandé dans la question.
Usage:
StreamUtil.takeWhile(IntStream.iterate(1, n -> n + 1), n -> n < 10);
Voici le code pour 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;
}
}
}
Allez chercher la bibliothèque AbacusUtil . Il fournit l’API exacte que vous voulez et plus encore:
IntStream.iterate(1, n -> n + 1).takeWhile(n -> n < 10).forEach(System.out::println);
Déclaration : Je suis le développeur de AbacusUtil.
Si vous connaissez le nombre exact de répétitions qui seront effectuées, vous pouvez faire
IntStream
.iterate(1, n -> n + 1)
.limit(10)
.forEach(System.out::println);
Même si j'avais une exigence similaire - invoquer le service Web. S'il échoue, réessayez-le 3 fois. Si cela échoue même après ces nombreux essais, envoyez une notification par courrier électronique. Après avoir beaucoup cherché sur Google, anyMatch()
est venu comme un sauveur. Mon exemple de code comme suit. Dans l'exemple suivant, si la méthode webServiceCall renvoie true à la première itération, stream ne se répète pas car nous avons appelé anyMatch()
. Je crois que c'est ce que vous recherchez.
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();
au lieu de pic, vous pouvez utiliser mapToObj pour renvoyer l'objet ou le message final
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);
Vous ne pouvez pas abandonner un flux sauf par un court-circuitage du terminal, ce qui laisserait certaines valeurs de flux non traitées, quelle que soit leur valeur. Mais si vous voulez simplement éviter les opérations sur un flux, vous pouvez ajouter une transformation et un filtre au flux:
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
Cela transforme le flux d'éléments en éléments NULL lorsque les éléments remplissent certaines conditions, puis élimine les éléments NULL. Si vous êtes prêt à vous laisser aller à des effets secondaires, vous pouvez définir la valeur de la condition sur true dès que quelque chose est rencontré. Ainsi, tous les éléments suivants sont filtrés, quelle que soit leur valeur. Mais même si ce n'est pas le cas, vous pouvez économiser beaucoup (sinon la totalité) du traitement en filtrant les valeurs du flux que vous ne souhaitez pas traiter.