How to Break from Java Stream forEach – 如何从Java Stream forEach中脱离出来

最后修改: 2019年 6月 25日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

As Java developers, we often write code that iterates over a set of elements and performs an operation on each one. The Java 8 streams library and its forEach method allow us to write that code in a clean, declarative manner.

作为 Java 开发人员,我们经常编写代码,在一组元素上进行迭代,并对每个元素执行操作。Java 8 streams 库及其forEach方法使我们能够以简洁、声明的方式编写该代码。

While this is similar to loops, we are missing the equivalent of the break statement to abort iteration. A stream can be very long, or potentially infinite, and if we have no reason to continue processing it, we would want to break from it, rather than wait for its last element.

虽然这与循环相似,但我们缺少与break语句相当的中止迭代的语句一个流可能非常长,或者可能是无限的,如果我们没有理由继续处理它,我们会想从它那里中断,而不是等待它的最后一个元素。

In this tutorial, we’re going to look at some mechanisms that allow us to simulate a break statement on a Stream.forEach operation.

在本教程中,我们将研究一些机制,允许我们在Stream.forEach操作上模拟一个break语句。

2. Java 9’s Stream.takeWhile()

2.Java 9的Stream.takeWhile()

Let’s suppose we have a stream of String items and we want to process its elements as long as their lengths are odd.

假设我们有一个String项的流,我们想处理其元素,只要它们的长度是奇数。

Let’s try the Java 9 Stream.takeWhile method:

让我们试试Java 9的Stream.takeWhile方法。

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

If we run this, we get the output:

如果我们运行这个,我们会得到输出。

cat
dog

Let’s compare this with the equivalent code in plain Java using a for loop and a break statement, to help us see how it works:

让我们将其与普通Java中使用for循环和break语句的同等代码进行比较,以帮助我们了解其工作原理。

List<String> 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);
}

As we can see, the takeWhile method allows us to achieve exactly what we need.

正如我们所看到的,takeWhile方法使我们能够实现我们所需要的东西。

But what if we haven’t adopted Java 9 yet? How can we achieve a similar thing using Java 8?

但是如果我们还没有采用Java 9呢?我们如何使用Java 8实现类似的事情呢?

3. A Custom Spliterator

3.一个自定义的分裂器

Let’s create a custom Spliterator that will work as a decorator for a Stream.spliteratorWe can make this Spliterator perform the break for us.

让我们创建一个自定义的Spliterator,它将作为Stream.spliteratordecorator工作。我们可以让这个Spliterator为我们执行break

First, we’ll get the Spliterator from our stream, then we’ll decorate it with our CustomSpliterator and provide the Predicate to control the break operation. Finally, we’ll create a new stream from the CustomSpliterator:

首先,我们将从我们的流中获取Spliterator,然后用我们的CustomSpliterator装饰它,并提供Predicate来控制break操作。最后,我们将从CustomSpliterator创建一个新的流:

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

Let’s look at how to create the CustomSpliterator:

让我们看看如何创建CustomSpliterator

public class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {

    private Spliterator<T> splitr;
    private Predicate<T> predicate;
    private boolean isMatched = true;

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

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

So, let’s take a look at the tryAdvance method. We can see here that the custom Spliterator processes the elements of the decorated Spliterator. The processing is done as long as our predicate is matched and the initial stream still has elements. When either of the conditions becomes false, our Spliterator “breaks” and the streaming operation ends.

所以,让我们看一下tryAdvance方法。我们可以看到,自定义的Spliterator处理了装饰的Spliterator的元素。只要我们的谓词被匹配并且初始流仍有元素,处理就会完成。当其中一个条件变成false时,我们的Spliterator“中断”,流式操作结束。

Let’s test our new helper method:

让我们测试一下我们的新辅助方法。

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

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

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

As we can see, the stream stopped after the condition was met. For testing purposes, we’ve collected the results into a list, but we could also have used a forEach call or any of the other functions of Stream.

我们可以看到,流在满足条件后停止了。为了测试,我们将结果收集到一个列表中,但我们也可以使用forEach调用或Stream的任何其他函数。

4. A Custom forEach

4.一个自定义的forEach

While providing a Stream with the break mechanism embedded can be useful, it may be simpler to focus on just the forEach operation.

虽然提供一个嵌入了break机制的Stream可能是有用的,但只关注forEach操作可能更简单。

Let’s use the Stream.spliterator directly without a decorator:

让我们直接使用Stream.spliterator ,不使用装饰器。

public class CustomForEach {

    public static class Breaker {
        private boolean shouldBreak = false;

        public void stop() {
            shouldBreak = true;
        }

        boolean get() {
            return shouldBreak;
        }
    }

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

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

As we can see, the new custom forEach method calls a BiConsumer providing our code with both the next element and a breaker object it can use to stop the stream.

我们可以看到,新的自定义forEach方法调用一个BiConsumer为我们的代码提供了下一个元素和一个可以用来停止流的breaker对象。

Let’s try this out in a unit test:

让我们在单元测试中试试这个。

@Test
public void whenCustomForEachIsCalled_ThenCorrectItemsAreReturned() {
    Stream<String> initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");
    List<String> 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. Conclusion

5.总结

In this article, we looked at ways to provide the equivalent of calling break on a stream. We saw how Java 9’s takeWhile solves most of the problem for us and how to provide a version of that for Java 8.

在这篇文章中,我们研究了在流上提供与调用break等效的方法。我们看到了Java 9的takeWhile是如何为我们解决大部分问题的,以及如何为Java 8提供一个这样的版本。

Finally, we looked at a utility method which can provide us with the equivalent of a break operation while iterating on a Stream.

最后,我们看了一个实用方法,它可以在迭代Stream时为我们提供相当于break的操作。

As always, the example code can be found over on GitHub.

一如既往,可以在GitHub上找到示例代码