Como sair do Java Stream forEach

Como sair do Java Stream forEach

1. Visão geral

Como desenvolvedores de Java, geralmente escrevemos código que itera sobre um conjunto de elementos e executa uma operação em cada um. OJava 8 streams librarye seu métodoforEach nos permitem escrever esse código de maneira clara e declarativa.

Embora seja semelhante a loops,we are missing the equivalent of the break statement to abort iteration. A stream can be very long, or potentially infinite, e se não tivermos razão para continuar processando, gostaríamos de interrompê-lo, em vez de esperar por seu último elemento.

Neste tutorial, vamos examinar alguns mecanismos queallow us to simulate a break statement on a Stream.forEach operation.

2. Stream.takeWhile() do Java 9

Vamos supor que temos um fluxo de itensString e queremos processar seus elementos, desde que seus comprimentos sejam ímpares.

Vamos tentar o método Java 9Stream.takeWhile:

Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck")
  .takeWhile(n -> n.length() % 2 != 0)
  .forEach(System.out::println);

Se executarmos isso, obteremos a saída:

cat
dog

Vamos comparar isso com o código equivalente em Java puro usando um loopfor e uma instruçãobreak, para nos ajudar a ver como funciona:

List list = asList("cat", "dog", "elephant", "fox", "rabbit", "duck");
for (int i = 0; i < list.size(); i++) {
    String item = list.get(i);
    if (item.length() % 2 == 0) {
        break;
    }
    System.out.println(item);
}

Como podemos ver, o métodotakeWhile nos permite alcançar exatamente o que precisamos.

Maswhat if we haven’t adopted Java 9 yet? Como podemos conseguir algo semelhante usando Java 8?

3. UmSpliterator personalizado

Let’s create a custom Spliterator that will work as a decorator for a Stream.spliteratorPodemos fazer esseSpliterator realizar obreak para nós.

Primeiro, vamos obter oSpliterator de nosso fluxo, então vamos decorá-lo com nossoCustomSpliteratore fornecer oPredicate para controlar a operaçãobreak. Por fim, criaremos um novo fluxo doCustomSpliterator:

public static  Stream takeWhile(Stream stream, Predicate predicate) {
    CustomSpliterator customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate);
    return StreamSupport.stream(customSpliterator, false);
}

Vejamos como criar oCustomSpliterator:

public class CustomSpliterator extends Spliterators.AbstractSpliterator {

    private Spliterator splitr;
    private Predicate predicate;
    private boolean isMatched = true;

    public CustomSpliterator(Spliterator splitr, Predicate predicate) {
        super(splitr.estimateSize(), 0);
        this.splitr = splitr;
        this.predicate = predicate;
    }

    @Override
    public synchronized boolean tryAdvance(Consumer consumer) {
        boolean hadNext = splitr.tryAdvance(elem -> {
            if (predicate.test(elem) && isMatched) {
                consumer.accept(elem);
            } else {
                isMatched = false;
            }
        });
        return hadNext && isMatched;
    }
}

Então, vamos dar uma olhada no métodotryAdvance. Podemos ver aqui que oSpliterator personalizado processa os elementos doSpliterator decorado. The processing is done as long as our predicate is matched and the initial stream still has elements. Quando qualquer uma das condições se tornafalse, nossoSpliterator“breaks” e a operação de streaming termina.

Vamos testar nosso novo método auxiliar:

@Test
public void whenCustomTakeWhileIsCalled_ThenCorrectItemsAreReturned() {
    Stream initialStream =
      Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");

    List result =
      CustomTakeWhile.takeWhile(initialStream, x -> x.length() % 2 != 0)
        .collect(Collectors.toList());

    assertEquals(asList("cat", "dog"), result);
}

Como podemos ver, o fluxo parou depois que a condição foi atendida. Para fins de teste, coletamos os resultados em uma lista, mas também poderíamos ter usado uma chamadaforEach ou qualquer uma das outras funções deStream.

4. UmforEach personalizado

Embora fornecerStream com o mecanismobreak incorporado possa ser útil, pode ser mais simples focar apenas na operaçãoforEach.

Vamos usarStream.spliterator diretamente sem um decorador:

public class CustomForEach {

    public static class Breaker {
        private boolean shouldBreak = false;

        public void stop() {
            shouldBreak = true;
        }

        boolean get() {
            return shouldBreak;
        }
    }

    public static  void forEach(Stream stream, BiConsumer consumer) {
        Spliterator spliterator = stream.spliterator();
        boolean hadNext = true;
        Breaker breaker = new Breaker();

        while (hadNext && !breaker.get()) {
            hadNext = spliterator.tryAdvance(elem -> {
                consumer.accept(elem, breaker);
            });
        }
    }
}

Como podemos ver, o novo métodoforEach personalizado chama umBiConsumer fornecendo ao nosso código o próximo elemento e um objeto disjuntor que ele pode usar para interromper o fluxo.

Vamos fazer um teste de unidade:

@Test
public void whenCustomForEachIsCalled_ThenCorrectItemsAreReturned() {
    Stream initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");
    List result = new ArrayList<>();

    CustomForEach.forEach(initialStream, (elem, breaker) -> {
        if (elem.length() % 2 == 0) {
            breaker.stop();
        } else {
            result.add(elem);
        }
    });

    assertEquals(asList("cat", "dog"), result);
}

5. Conclusão

Neste artigo, examinamos maneiras de fornecer o equivalente a chamarbreak em um fluxo. Vimos comotakeWhile do Java 9 resolve a maioria dos problemas para nós e como fornecer uma versão disso para o Java 8.

Finalmente, vimos um método utilitário que pode nos fornecer o equivalente a uma operaçãobreak enquanto iteramos emStream.

Como sempre, o código de exemplo pode ser encontradoover on GitHub.