繁体   English   中英

在Java 8 Streams上实现自定义中间操作

[英]Implementing custom intermediate operations on Java 8 Streams

我正在尝试弄清楚如何在Java 8 Stream上实现自定义的中间操作。 看来我被封锁了:(

具体来说,我想获取一个流并返回直到并包括具有特定值的第一个条目的每个条目。 而且我想在此之后停止生成任何内容-使其短路。

它正在对输入数据进行一系列验证检查。 如果有一个错误,我想停在第一个错误上,但是我想在路上整理警告。 而且由于这些验证检查可能很昂贵-例如涉及数据库查找-我只想运行所需的最小设置。

因此,代码将类似于:

Optional<ValidationResult> result = validators.stream()
    .map(validator -> validator.validate(data))
    .takeUntil(result -> result.isError()) // This is the bit I can't do
    .reduce(new ValidationResult(), ::mergeResults);

似乎我应该能够使用ReferencePipeline.StatefulOp进行某些操作,除了它是所有包范围之外,因此我无法对其进行扩展。 所以我想知道实现这一目标的正确方法是什么? 还是有可能?

还要注意-这需要使用Java 8,而不是9+,因为由于各种不相关的原因我们还没有出现。

干杯

通常,自定义操作将需要处理Spliterator接口。 它通过添加特征和大小信息以及将元素的一部分拆分为另一个拆分器的能力(因此得名)扩展了Iterator的概念。 它也只需要一种方法就简化了迭代逻辑。

public static <T> Stream<T> takeWhile(Stream<T> s, Predicate<? super T> condition) {
    boolean parallel = s.isParallel();
    Spliterator<T> spliterator = s.spliterator();
    return StreamSupport.stream(new Spliterators.AbstractSpliterator<T>(
        spliterator.estimateSize(),
        spliterator.characteristics()&~(Spliterator.SIZED|Spliterator.SUBSIZED)) {
            boolean active = true;
            Consumer<? super T> current;
            Consumer<T> adapter = t -> {
                if((active = condition.test(t))) current.accept(t);
            };

            @Override
            public boolean tryAdvance(Consumer<? super T> action) {
                if(!active) return false;
                current = action;
                try {
                    return spliterator.tryAdvance(adapter) && active;
                }
                finally {
                    current = null;
                }
            }
        }, parallel).onClose(s::close);
}

为了保留流的属性,我们首先查询并行状态,以为新流重新建立并行状态。 另外,我们注册了一个close操作,它将关闭原始流。

主要工作是实现Spliterator装饰先前的流状态的分隔器。

除了SIZEDSUBSIZED以外,其他特征都会保留SUBSIZED ,因为我们的操作会导致大小无法预测。 原始大小仍会通过,现在将用作估计值。

此解决方案在操作期间存储传递给tryAdvanceConsumer ,以便能够使用同一适配器使用者,从而避免为每次迭代创建一个新的使用者。 这是tryAdvance ,因为可以确保不会同时调用tryAdvance

并行是通过拆分完成的,拆分是从AbstractSpliterator继承的。 这种继承的实现将缓冲某些元素,这是合理的,因为为类似takeWhile的操作实现更好的策略确实很复杂。

所以你可以像这样使用它

    takeWhile(Stream.of("foo", "bar", "baz", "hello", "world"), s -> s.length() == 3)
        .forEach(System.out::println);

将打印

foo
bar
baz

要么

takeWhile(Stream.of("foo", "bar", "baz", "hello", "world")
    .peek(s -> System.out.println("before takeWhile: "+s)), s -> s.length() == 3)
    .peek(s -> System.out.println("after takeWhile: "+s))
    .forEach(System.out::println);

将打印

before takeWhile: foo
after takeWhile: foo
foo
before takeWhile: bar
after takeWhile: bar
bar
before takeWhile: baz
after takeWhile: baz
baz
before takeWhile: hello

这表明它处理的内容超出了必要。 takeWhile阶段之前,我们必须遇到第一个不匹配的元素,此后,我们仅遇到直到该元素的元素。

我承认在代码方面明智的做法,Holger的答案要性感得多,但也许这在某种程度上更易于阅读:

public static <T> Stream<T> takeUntilIncluding(Stream<T> s, Predicate<? super T> condition) {

    class Box implements Consumer<T> {

        boolean stop = false;

        T t;

        @Override
        public void accept(T t) {
            this.t = t;
        }
    }

    Box box = new Box();

    Spliterator<T> original = s.spliterator();

    return StreamSupport.stream(new AbstractSpliterator<>(
        original.estimateSize(),
        original.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED)) {

        @Override
        public boolean tryAdvance(Consumer<? super T> action) {

            if (!box.stop && original.tryAdvance(box) && condition.test(box.t)) {
                action.accept(box.t);
                return true;
            }

            box.stop = true;

            return false;
        }
    }, s.isParallel());

}

您可以使用以下结构;

AtomicBoolean gateKeeper = new AtomicBoolean(true);    
Optional<Foo> result = validators.stream()
    .filter(validator -> gateKeeper.get() 
                && gateKeeper.compareAndSet(true, !validator.validate(data).isError()) 
                && gateKeeper.get())
    .reduce(...) //have the first n non-error validators here

带有gateKeeper的过滤器用作短路逻辑,一直运行直到遇到第一个isError() == true情况,然后拒绝它,然后关闭此后进行其他validate()调用的门。 它看起来有些疯狂,但是它比其他自定义实现要简单得多,并且如果适合您的要求,它可能会完美地工作。

无法isError()确定这是否有帮助,因为除了isError()结果之外,我忽略了validator.validate(data) isError()结果,而且它不属于列表中的任何一个validator

您可以使用技巧:

List<ValidationResult> res = new ArrayList<>(); // Can modify it with your `mergeResults` instead of list

Optional<ValidationResult> result = validators.stream()
    .map(validator -> validator.validate(data))
    .map(v -> {
       res.add(v);
       return v;
    })
    .filter(result -> result.isError())
    .findFirst();

List<ValidationResult> res将包含您感兴趣的值。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM