java stream短路_java – 如何在Stream上短路一个reduce()操作?

不幸的是,Stream API具有创建自己的短路操作的能力有限。不太干净的解决方案是抛出一个RuntimeException并捕获它。这是IntStream的实现,但它也可以推广用于其他流类型:

public static int reduceWithCancelEx(IntStream stream, int identity,

IntBinaryOperator combiner, IntPredicate cancelCondition) {

class CancelException extends RuntimeException {

private final int val;

CancelException(int val) {

this.val = val;

}

}

try {

return stream.reduce(identity, (a, b) -> {

int res = combiner.applyAsInt(a, b);

if(cancelCondition.test(res))

throw new CancelException(res);

return res;

});

} catch (CancelException e) {

return e.val;

}

}

用法示例:

int product = reduceWithCancelEx(

IntStream.of(2, 3, 4, 5, 0, 7, 8).peek(System.out::println),

1, (a, b) -> a * b, val -> val == 0);

System.out.println("Result: "+product);

输出:

2

3

4

5

0

Result: 0

请注意,即使它与并行流一起工作,一旦其中一个引发异常,就不能保证其他并行任务将完成。已经开始的子任务可能会运行到完成,因此您可以处理比预期更多的元素。

更新:替代解决方案更长,但更平行友好。它基于定制拼接器,它最多返回一个元素,这是所有底层元素的累积结果)。当您在顺序模式下使用它时,它会在单个tryAdvance调用中执行所有操作。当您拆分时,每个部分都会生成对应的单个部分结果,这些结果由Stream引擎使用组合器功能减少。这是通用版本,但是原始专业化也是可能的。

final static class CancellableReduceSpliterator implements Spliterator,

Consumer, Cloneable {

private Spliterator source;

private final BiFunction accumulator;

private final Predicate cancelPredicate;

private final AtomicBoolean cancelled = new AtomicBoolean();

private A acc;

CancellableReduceSpliterator(Spliterator source, A identity,

BiFunction accumulator, Predicate cancelPredicate) {

this.source = source;

this.acc = identity;

this.accumulator = accumulator;

this.cancelPredicate = cancelPredicate;

}

@Override

public boolean tryAdvance(Consumer super A> action) {

if (source == null || cancelled.get()) {

source = null;

return false;

}

while (!cancelled.get() && source.tryAdvance(this)) {

if (cancelPredicate.test(acc)) {

cancelled.set(true);

break;

}

}

source = null;

action.accept(acc);

return true;

}

@Override

public void forEachRemaining(Consumer super A> action) {

tryAdvance(action);

}

@Override

public Spliterator trySplit() {

if(source == null || cancelled.get()) {

source = null;

return null;

}

Spliterator prefix = source.trySplit();

if (prefix == null)

return null;

try {

@SuppressWarnings("unchecked")

CancellableReduceSpliterator result =

(CancellableReduceSpliterator) this.clone();

result.source = prefix;

return result;

} catch (CloneNotSupportedException e) {

throw new InternalError();

}

}

@Override

public long estimateSize() {

// let's pretend we have the same number of elements

// as the source, so the pipeline engine parallelize it in the same way

return source == null ? 0 : source.estimateSize();

}

@Override

public int characteristics() {

return source == null ? SIZED : source.characteristics() & ORDERED;

}

@Override

public void accept(T t) {

this.acc = accumulator.apply(this.acc, t);

}

}

public static U reduceWithCancel(Stream stream, U identity,

BiFunction accumulator, BinaryOperator combiner,

Predicate cancelPredicate) {

return StreamSupport

.stream(new CancellableReduceSpliterator<>(stream.spliterator(), identity,

accumulator, cancelPredicate), stream.isParallel()).reduce(combiner)

.orElse(identity);

}

public static T reduceWithCancel(Stream stream, T identity,

BinaryOperator combiner, Predicate cancelPredicate) {

return reduceWithCancel(stream, identity, combiner, combiner, cancelPredicate);

}

我们来测试这两个版本,并计算实际处理的元素数量。让我们把0接近尾声。例外版本:

AtomicInteger count = new AtomicInteger();

int product = reduceWithCancelEx(

IntStream.range(-1000000, 100).filter(x -> x == 0 || x % 2 != 0)

.parallel().peek(i -> count.incrementAndGet()), 1,

(a, b) -> a * b, x -> x == 0);

System.out.println("product: " + product + "/count: " + count);

Thread.sleep(1000);

System.out.println("product: " + product + "/count: " + count);

典型输出:

product: 0/count: 281721

product: 0/count: 500001

所以当只处理一些元素时返回结果,这些任务在后台继续工作,计数器仍在增加。这里是spliterator版本:

AtomicInteger count = new AtomicInteger();

int product = reduceWithCancel(

IntStream.range(-1000000, 100).filter(x -> x == 0 || x % 2 != 0)

.parallel().peek(i -> count.incrementAndGet()).boxed(),

1, (a, b) -> a * b, x -> x == 0);

System.out.println("product: " + product + "/count: " + count);

Thread.sleep(1000);

System.out.println("product: " + product + "/count: " + count);

典型输出:

product: 0/count: 281353

product: 0/count: 281353

当返回结果时,所有的任务实际上完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值