【问题标题】:How to match stream elements but return false if non exists?如何匹配流元素但如果不存在则返回false?
【发布时间】:2017-09-06 12:52:18
【问题描述】:

我有一个流,想检查是否都匹配过滤器。如果都匹配,则返回true

但是,如果流是空的,我想返回false

我该怎么做?

示例代码:

public boolean validate(Stream<Whatever> stream) {
  // Problem: returns **true** if stream empty.
  // How can **false** be returned if stream is empty?
  return stream.allMatch(Whatever::someCheck);
}

【问题讨论】:

  • 或可选的,你读过吗?
  • 你的意思是当给定流为空时你想返回false?
  • @user1803551 是,但如果不为空,则继续匹配。

标签: java java-8 java-stream


【解决方案1】:

你可以使用

public boolean validate(Stream<Whatever> stream) {
    return stream.map(Whatever::someCheck).reduce(Boolean::logicalAnd).orElse(false);
}

表达意图。我们将每个元素映射到一个 boolean 值,表示它是否匹配,并使用逻辑 and 操作来减少所有元素,如果它们都是true,则将产生true。如果没有元素,reduce 将返回一个空的Optional,我们使用orElse(false) 将其映射到false,正如预期的那样。

唯一的缺点是这是非短路的,即不会立即在第一个不匹配的元素处停止。

仍然支持短路的解决方案可能会更加完善:

public boolean validate(Stream<Whatever> stream) {
    boolean parallel = stream.isParallel();
    Spliterator<Whatever> sp = stream.spliterator();
    if(sp.getExactSizeIfKnown() == 0) return false;
    Stream.Builder<Whatever> b = Stream.builder();
    if(!sp.tryAdvance(b)) return false;
    return Stream.concat(b.build(), StreamSupport.stream(sp, parallel))
        .allMatch(Whatever::someCheck);
}

这是Eugene’s answer 的变体,但它不会丢失特性或并行性,并且总体上可能更高效。

【讨论】:

  • 您可以用任何复合验证替换Whatever::someCheck。链接多个filter 调用与通过&amp;&amp; 组合条件没有什么不同,例如在 lambda 表达式中。除了创建复合谓词之外,您的初始方法也无法实现其他解决方案。在您调用 allMatch 后,该流已被使用,无法进行其他验证。
  • 有没有办法保留allMatch()?我认为它比map().reduce(Boolean::logicalAnd)更具描述性。
  • 嗯,第二个变体有allMatch 调用。没有其他干净的方法,因为allMatch 只是一个不透明的终端操作,不支持与另一个评估相结合。
  • @Holger 哇哦!第二种方法很好,所以。非常好!我不知道BuilderConsumer,这使得实现在这里变得轻而易举
  • spliterator 虽然是正式的终端操作。我想这意味着不需要实现来保持惰性。
【解决方案2】:

下面的代码可以工作(我测试过)。

public static boolean validate(Stream<Whatever> stream) {
    return stream.reduce((whatever1, whatever2) -> Whatever.someCheck(whatever1) ? whatever2 : whatever1)
            .map(Whatever::someCheck).orElse(false);
}

它是如何工作的?我们使用reduce 操作来检查每个元素是否与谓词匹配,如果失败,我们会继续返回失败的元素(在三元操作中)。最后,我们将缩减后的Whatever 对象映射为布尔值,如果为真:则全部匹配且不为空(orElse(false))。

【讨论】:

  • ...这与我的.map(Whatever::someCheck).reduce(Boolean::logicalAnd).orElse(false) 没有什么不同,只是您可以在同一个对象上多次调用someCheck,但是,someCheck 调用的总数将是相同的。
  • @Holger 我已经在你之前几秒钟发布了我的答案。但你是对的,这就是为什么我也已经支持你的答案了。
  • 你的变体是一个有趣的指针,指向查询第一个失败元素的可能性。
【解决方案3】:

如果您对失去自己的特征和并行性感到满意,例如:

 public static boolean validate(Stream<String> stream) {
    Iterator<String> it = stream.iterator();

    if (!it.hasNext()) {
        return false;
    }

    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, 0), false)
            .peek(System.out::println)
            .allMatch(x -> x.contains("a"));

}

【讨论】:

  • iterator 是正式的终端操作,虽然它可能不是在实践中。
  • @JornVernee 是的,我在想你想从那个 Stream 中检查多少元素
  • 您可以通过使用Spliterator 而不是Iterator 来解决您的解决方案的缺陷。 My answer 包含这样的解决方案。剩下的问题是短路是否值得失去非短路解决方案的清晰度……
【解决方案4】:

试试peek

boolean[] flag = new boolean[1];
return stream.peek(t -> flag[0] = true).allMatch(Whatever::someCheck) && flag[0]

【讨论】:

  • 谢谢,更新到新的boolean[1]。你能举个例子,它可能不适用于顺序流吗?以及何时/为什么不能保证它在 && flag[0] 处不可见?还是您的意思是flag 将被复制到并行流中的子线程?
  • 我认为这对于顺序流来说会很好,即使在 Java 9 中允许进行新的优化。我不太确定在并行流的情况下对共享状态的非易失性访问 - 可能是值得一个单独的问题。
  • 主线程也将参与流评估,因此它并发运行到子线程,但是,有一个 happens-before 是正确的 子任务执行的动作与allMatch返回的结果之间的关系。这在短路时不适用,但由于这只能在返回false 时更早返回,所以这里不是问题。所以这段代码可以工作,但如果有人试图调整它以将其重用于另一个用例,它可能会中断。
  • @123-xyz:主线程将贡献,否则,它是一个未使用的资源。但是请注意,公共池具有创建“核心数减一”工作线程的默认配置,因此总并发通常与核心数相匹配。 “短路”意味着操作可能在不处理所有元素的情况下完成。它仍然会取消挂起的子作业并等待它们的终止,但这不一定足以建立未使用结果的子作业的peek 操作的 happens-before 关系,并且主线程。
  • 但是如前所述,这不是问题。 allMatch 只能在一个元素的计算结果为 false 时更早完成,因此 flag 是无关紧要的,在计算为 false 之前的 peek 将执行 true 到数组的可见写入,因为所有线程仅将true 写入数组,不能有矛盾的写入。如果您想知道哪些操作可能会短路,请查看the documentation 并搜索“短路”...
【解决方案5】:

我已经添加了两个我现在删除的答案,但问题一直困扰着我,所以我第三次尝试,这次它可以工作,包括保留allMatch。这是方法validate的代码。

public static boolean validate(Stream<Whatever> stream) {
    final boolean[] streamNotEmpty = new boolean[1];
    return stream.filter(elem -> {
        streamNotEmpty[0] = true;
        return true;
    }).allMatch(Whatever::someCheck) && streamNotEmpty[0];
}

更短的版本是

public static boolean validate(Stream<Whatever> stream) {
    final boolean[] streamNotEmpty = new boolean[1];
    return stream.allMatch(elem -> {
        streamNotEmpty[0] = true;
        return elem.someCheck();
    }) && streamNotEmpty[0];
}

这背后的想法是我们想知道流中是否至少有一个元素,所以我创建了一个final boolean[],它的值在过滤器调用中发生了变化。因此,在流处理结束时,我们可以检查流是否为空,并在返回结果时采取相应措施。

这是一个完整的课程,包括测试用例来证明 - 这一次终于 - 我提供了一个有效的解决方案。

import java.util.ArrayList;
import java.util.stream.Stream;

public class StreamTest {

    public static boolean validate(Stream<Whatever> stream) {
        final boolean[] streamNotEmpty = new boolean[1];
        return stream.allMatch(elem -> {
            streamNotEmpty[0] = true;
            return elem.someCheck();
        }) && streamNotEmpty[0];
    }

    static class Whatever {
        private boolean checkResult;

        public Whatever() {
            this(false);
        }

        public Whatever(boolean b) {
            this.checkResult = b;
        }

        public boolean someCheck() {
            return checkResult;
        }
    }

    public final static void main(String[] args) {
        ArrayList<Whatever> list = new ArrayList<>();
        System.out.println("empty list: " + validate(list.stream()));
        System.out.println("empty list parallel: " + validate(list.parallelStream()));

        for (int i = 0; i < 10000; i++) {
            list.add(new Whatever(true));
        }

        System.out.println("non empty true list: " + validate(list.stream()));
        System.out.println("non empty true list parallel: " + validate(list.parallelStream()));

        for (int i = 0; i < 10000; i += 1000) {
            list.add(new Whatever(false));
        }

        System.out.println("non empty false list: " + validate(list.stream()));
        System.out.println("non empty false list parallel: " + validate(list.parallelStream()));
    }
}

执行时的输出是:

empty list: false empty list parallel: false non empty true list: true non empty true list parallel: true non empty false list: false non empty false list parallel: false

【讨论】:

  • 这与this answer 基本相同,除了(ab-)使用filter()allMatch() 产生副作用,这可以说比仅使用peek() 更糟糕。
【解决方案6】:
return !stream.filter(Whatever::someCheck).collect(Collectors.toList()).isEmpty()

【讨论】:

  • 也许.count() 更好,因为您不必创建一个新列表来查看它是否为空
  • 收集到集合的问题是,如果流有很多元素,它可能会导致OOME(OutOfMemoryError)。
  • 但是 OP 想知道是否所有元素都匹配。您的代码检查是否有任何元素匹配。
  • @Eran 我刚刚意识到并删除了答案的那部分。
  • 这只是表达.filter(Whatever::someCheck).count() &gt; 0的低效方式,这已经是表达.anyMatch(Whatever::someCheck)的低效方式,这不是OP想要的......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-17
  • 2020-01-07
  • 2021-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多