在单个流上组合allMatch,noneMatch和anyMatch

sla*_*hms 4 java java-8 java-stream

我想有以下逻辑:(我知道它不起作用,因为它不止一次地消耗流).但我不知道如何实现它.

Stream<ByteBuffer> buffers = super.getBuffers().stream();
if (buffers.allMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.FULLY_SENT;
} else if (buffers.noneMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.WAS_NOT_SENT;
} else {
    return OutgoingMessageStatus.PARTIALLY_SENT;
}
Run Code Online (Sandbox Code Playgroud)

我怎样才能做到这一点?

Hol*_*ger 9

由于结果super.getBuffers()List<ByteBuffer>,您可以迭代两次.

List<ByteBuffer> buffers = super.getBuffers();
if (buffers.stream().allMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.FULLY_SENT;
} else if (buffers.stream().noneMatch(b -> b.position() > 0)) {
    return OutgoingMessageStatus.WAS_NOT_SENT;
} else {
    return OutgoingMessageStatus.PARTIALLY_SENT;
}
Run Code Online (Sandbox Code Playgroud)

请注意,在所有情况下,这仍然不需要迭代所有元素.allMatch遇到非匹配元素后立即noneMatch返回,遇到匹配元素后立即返回.因此,在这种PARTIALLY_SENT情况下,有可能在不查看所有元素的情况下得出结论.

另一种选择是

List<ByteBuffer> buffers = super.getBuffers();
if(buffers.isEmpty()) return OutgoingMessageStatus.FULLY_SENT;
Predicate<ByteBuffer> p = b -> b.position() > 0;
boolean sent = p.test(buffers.get(0));
if(!sent) p = p.negate();
return buffers.stream().skip(1).allMatch(p)? sent?
    OutgoingMessageStatus.FULLY_SENT:
    OutgoingMessageStatus.WAS_NOT_SENT:
    OutgoingMessageStatus.PARTIALLY_SENT;
}
Run Code Online (Sandbox Code Playgroud)

第一个元素的状态决定了我们必须检查的条件.一旦存在矛盾的因素,allMatch立即返回并且我们有一个PARTIALLY_SENT情况.否则,所有元素都匹配第一个,表示"全部已发送"或"未发送".

对空列表进行预检查会产生与原始代码相同的行为,并确保get(0)永不中断.


如果您确实拥有Stream而不是可以多次迭代的源,那么就没有简单的快捷解决方案,因为这需要有状态谓词.但是,有一些处理所有元素的简单解决方案.

Map<Boolean,Long> result=getBuffers().stream()
    .collect(Collectors.partitioningBy(b -> b.position() > 0, Collectors.counting()));
return
    result.getOrDefault(false, 0L)==0?
        OutgoingMessageStatus.FULLY_SENT:
    result.getOrDefault(true, 0L)==0?
        OutgoingMessageStatus.WAS_NOT_SENT:
        OutgoingMessageStatus.PARTIALLY_SENT;
Run Code Online (Sandbox Code Playgroud)

要么

return super.getBuffers().stream()
    .map(b -> b.position() > 0?
              OutgoingMessageStatus.FULLY_SENT: OutgoingMessageStatus.WAS_NOT_SENT)
    .reduce((a,b) -> a==b? a: OutgoingMessageStatus.PARTIALLY_SENT)
    .orElse(OutgoingMessageStatus.FULLY_SENT);
Run Code Online (Sandbox Code Playgroud)

  • @Stuart Marks:我知道.但由于没有明确指定,我决定在这里使用`getOrDefault`,这不会受到伤害.顺便说一句,[partitioningBy`返回的特殊地图](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/stream/Collectors .java#1540)*应该*覆盖一些典型的`Map`操作.目前,它只实现了`entrySet()`,它甚至使一个简单的`get`(带有`Boolean`)成为一个非常昂贵的操作,特别是考虑到每次实例化一个*new*入口集,即使是一个简单的`大小()`电话. (3认同)
  • @Holger规范变更由[JDK-8170943](https://bugs.openjdk.java.net/browse/JDK-8170943)涵盖,集成在JDK 9 build 150(2016-12-22发布)中.[JDK-8170945](https://bugs.openjdk.java.net/browse/JDK-8170945)涵盖了优化.不知道什么时候它会被修复,但它会在某个时候开始工作. (3认同)
  • @Stuart Marks:关于JDK-8170945,`containsKey`有`Object`参数类型,所以它必须是`public boolean containsKey(Object key){return key instanceof Boolean; }`.此外,我还投票给`@Override public void forEach(BiConsumer <?super Boolean,?super V> c){c.accept(Boolean.FALSE,forFalse); c.accept(Boolean.TRUE,forTrue); 要释放内部迭代的好处,好吧,如果我们心情愉快,也可以使用`@Override public boolean containsValue(Object value){return Objects.equals(forTrue,value)|| Objects.equals(forFalse,value); }`... (3认同)
  • 正如[评论](http://stackoverflow.com/questions/41036522/combine-allmatch-nonematch-and-anymatch-on-a-single-stream/41036977#comment69281575_41036598)中所述,`allMatch`在第一个停止非匹配元素,`noneMatch`在第一个匹配元素处停止.因此,这两个操作中的一个将在第一个元素处停止.第二个仍然可能会提前停止,这就是`PARTIALLY_SENT`场景中的情况.我的第二个优化解决方案也是如此,它只减少了一个支票的数量.没有解决方案可以执行较少的检查. (2认同)
  • @Holger最后一段代码绝对恶心!+1 (2认同)
  • 是的,我只是查看Collectors $ Partition,我也注意到缺少的替代。看起来有些优化可以在这里应用。内部实现依赖于两个条目都存在,并且简化了事情,因此也许也应将其纳入规范中。 (2认同)
  • @Holger D'哦,当然这些方法不是通用的!另外,感谢您的其他建议. (2认同)

Era*_*ran 6

您可以使用filter(),然后计算通过它的元素数量:

Stream<ByteBuffer> buffers = super.getBuffers().stream();
int matches = buffers.filter(b -> b.position() > 0).count();
if (matches == super.getBuffers().size()) {
    return OutgoingMessageStatus.FULLY_SENT;
} else if (matches == 0) {
    return OutgoingMessageStatus.WAS_NOT_SENT;
} else {
    return OutgoingMessageStatus.PARTIALLY_SENT;
}
Run Code Online (Sandbox Code Playgroud)

这假设Stream( super.getBuffers())的数据源有一个size()方法。如果没有,您可以ByteBuffer使用附加变量(不太优雅,我知道)计算s的总数:

int[] total = {0};
int matches = buffers.filter(b -> {total[0]++; return b.position() > 0;}).count();
if (matches == total[0]) {
    return OutgoingMessageStatus.FULLY_SENT;
} else if (matches == 0) {
    return OutgoingMessageStatus.WAS_NOT_SENT;
} else {
    return OutgoingMessageStatus.PARTIALLY_SENT;
}
Run Code Online (Sandbox Code Playgroud)

这种方法的缺点是当只有一些元素通过过滤器时它不会很快失败(即输出应该是OutgoingMessageStatus.PARTIALLY_SENT)。也许您可以使用一些reduce操作来返回三个可能的输出之一,并且仅根据需要处理尽可能多的元素。

  • 请注意,[迭代两次](http://stackoverflow.com/a/41036977/2711488) 是最有效的方法。由于第一个元素只能是匹配或不匹配,因此在检查第一个元素后,`allMatch` 或 `nonMatch` 中的任何一个都会立即返回。如果需要,另一个检查将是短路。所以它最多有 *n* + 1 个检查,但可能更少。我还提供了一个最多包含 *n* 个检查的短路解决方案。由于您的解决方案需要存在一个 `size()` 方法,它也依赖于 `super.getBuffers()` 返回一个集合。 (2认同)
  • 我添加了一个基于 `reduce` 的解决方案,我喜欢它的清晰性,但是在 Stream API 中没有捷径 `reduce`(我知道,这之前已经被其他人请求过)……我还添加了一个 `partitioningBy ` 解决方案,这是您的第二个变体的干净版本。 (2认同)