是否有可能编写一个Java Collector,当它有结果时会提前退出?

时间:2018-03-23 19:05:36

标签: java java-8 java-stream

是否可以实现一个收集器,一旦答案可用就停止处理流?

例如,如果收集器正在计算平均值,并且其中一个值是NaN,我知道答案将是NaN而不再看到任何值,因此进一步的计算是没有意义的。

4 个答案:

答案 0 :(得分:2)

除了Federico的评论之外,一旦满足某个条件,就可以通过停止累积来模拟短路Collector。但是,这种方法只有在累积成本高的情况下才有用。这是一个例子,但请记住,这种实现存在缺陷:

public class AveragingCollector implements Collector<Double, double[], Double> {
    private final AtomicBoolean hasFoundNaN = new AtomicBoolean();

    @Override
    public Supplier<double[]> supplier() {
        return () -> new double[2];
    }

    @Override
    public BiConsumer<double[], Double> accumulator() {
        return (a, b) -> {
            if (hasFoundNaN.get()) {
                return;
            }

            if (b.equals(Double.NaN)) {
                hasFoundNaN.set(true);
                return;
            }

            a[0] += b;
            a[1]++;
        };
    }

    @Override
    public BinaryOperator<double[]> combiner() {
        return (a, b) -> {
            a[0] += b[0];
            a[1] += b[1];

            return a;
        };
    }

    @Override
    public Function<double[], Double> finisher() {
        return average -> average[0] / average[1];
    }

    @Override
    public Set<Characteristics> characteristics() {
        return new HashSet<>();
    }
}

以下用例按预期返回Double.NaN

public static void main(String args[]) throws IOException {
    DoubleStream.of(1, 2, 3, 4, 5, 6, 7, Double.NaN)
                .boxed()
                .collect(new AveragingCollector()));
}

答案 1 :(得分:1)

感谢您的回复。这些评论为解决方案指明了方向,我将在此进行描述。它受到了StreamEx的启发,但适应了我的特殊情况。

首先,我定义了一个名为XdmStream的Stream实现,它通常将所有方法委托给它所包含的基础流。

这让我有机会定义新方法,例如我的用户可以stream.last()代替stream.reduce((first,second)->second),这是一种非常有用的便利。

作为短路方法的一个例子,我实现了XdmStream.untilFirst(Predicate)如下(base是包装的流)。这种方法的想法是返回一个流,该流提供与原始流相同的结果,除了当满足谓词时,不再传递结果。

public XdmStream<T> untilFirst(Predicate<? super XdmItem> predicate) {
    Stream<T> stoppable = base.peek(item -> {
        if (predicate.test(item)) {
            base.close();
        }
    });
    return new XdmStream<T>(stoppable);
}

当我第一次创建基本流时,我调用其onClose()方法,以便对close()的调用触发数据供应商停止提供数据。

close()机制似乎没有特别好记录(它依赖于“流管道”的概念,并且当某个方法返回的新流是与该管道相同的管道的一部分时,并不完全清楚原始流) - 但它对我有用。我想我应该确保这只是一个优化,因此即使数据流没有立即关闭(例如,如果流中有任何缓冲),结果仍然是正确的。

答案 2 :(得分:0)

对于NaN的情况,可以接受将此视为异常结果,因此抛出自定义NaNAverageException,使收集操作短路。通常使用异常进行正常控制流程是一种不好的做法,但在这种情况下可能是合理的。

答案 3 :(得分:0)

Stream<String> s = Stream.of("1","2","ABC", "3");
    try
    {
        double result = s.collect(Collectors.averagingInt(n -> Integer.parseInt(n)));
        System.err.println("Average :"+ result);
    }
    catch (NumberFormatException e)
    {
        // exception will be thrown it encounters ABC and collector won't go for "3"
        e.printStackTrace();
    }