Java流foreach循环break操作
1. 概述
作为 Java 开发人员,我们经常编写迭代一组元素并对每个元素执行操作的代码。Java 8 流库 及其*forEach 方法允许我们以干净、声明的方式编写该代码。 虽然这类似于循环,但 **我们缺少相当于 中止迭代的break***语句。一个流可以很长,或者可能是无限的,如果我们没有理由继续处理它,我们会想要从它中分离出来,而不是等待它的最后一个元素。
在本教程中,我们将了解一些允许我们在Stream.forEach操作上模拟break语句的机制。
2. Java 9 的Stream.takeWhile()
假设我们有一个String项目流,我们希望处理其长度为奇数的元素。
让我们试试 Java 9 Stream.takeWhile方法:
Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck")
.takeWhile(n -> n.length() % 2 != 0)
.forEach(System.out::println);
如果我们运行它,我们会得到输出:
cat
dog
让我们将其与使用for循环和break语句的普通 Java 中的等效代码进行比较,以帮助我们了解它是如何工作的:
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);
}
正如我们所见, takeWhile方法使我们能够准确地实现我们所需要的。
但是**如果我们还没有采用 Java 9 呢?**我们如何使用 Java 8 实现类似的事情?
3. 自定义Spliterator
**让我们创建一个自定义Spliterator ,它将作为Stream.spliterator的装饰器 。**我们可以让这个Spliterator为我们执行break。
首先,我们将从流中获取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);
}
让我们看看如何创建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;
}
}
那么,让我们看一下tryAdvance方法。我们可以在这里看到自定义的Spliterator处理装饰后的Spliterator的元素。**只要我们的谓词匹配并且初始流仍然有元素,处理就会完成。**当任一条件变为false时,我们的Spliterator *“break”*并且流操作结束。
让我们测试一下我们的新辅助方法:
@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);
}
如我们所见,流在满足条件后停止。出于测试目的,我们已将结果收集到一个列表中,但我们也可以使用forEach调用或Stream的任何其他函数。
4. 每个人的习惯
虽然提供一个嵌入break机制的Stream可能很有用,但只关注forEach操作可能更简单。
让我们在没有装饰器的情况下直接使用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);
});
}
}
}
如我们所见,新的自定义 forEach方法调用 BiConsumer 为我们的代码提供下一个元素和可用于停止流的断路器对象。
让我们在单元测试中尝试一下:
@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);
}