如何从 Java Stream forEach 中 跳 出来

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*,它将作为Stream.spliterator*的*装饰器 我们可以让这个Spliterator为我们执行中断

首先,我们将从流中获取Spliterator ,然后用**CustomSpliterator装饰它并提供Predicate来控制中断操作。最后,我们将从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 *“中断”*并且流操作结束。

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

@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.每个人的习惯

虽然提供一个嵌入中断机制的**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);
}

5.结论

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

最后,我们研究了一种实用方法,它可以在迭代Stream时为我们提供相当于中断操作的功能 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值