java 流是否能够从映射/过滤条件中延迟减少?

Aar*_*onC 3 java lazy-evaluation java-8 java-stream

我正在使用函数式编程风格来解决 Leetcode 的简单问题,计算一致字符串的数量。这个问题的前提很简单:计算谓词“所有值都在另一个集合中”成立的值的数量。

我有两种方法,一种我相当确定其行为符合我的要求,另一种我不太确定。两者都会产生正确的输出,但理想情况下,它们会在输出处于最终状态后停止评估其他元素。


    public int countConsistentStrings(String allowed, String[] words) {
        final Set<Character> set = allowed.chars()
          .mapToObj(c -> (char)c)
          .collect(Collectors.toCollection(HashSet::new));
        return (int)Arrays.stream(words)
          .filter(word ->
                  word.chars()
                  .allMatch(c -> set.contains((char)c))
                 )
          .count();
    }
Run Code Online (Sandbox Code Playgroud)

在此解决方案中,据我所知,allMatch 语句将在谓词不成立的 c 的第一个实例处终止并计算为 false,从而跳过该流中的其他值。


    public int countConsistentStrings(String allowed, String[] words) {
        Set<Character> set = allowed.chars()
          .mapToObj(c -> (char)c)
          .collect(Collectors.toCollection(HashSet::new));
        return (int)Arrays.stream(words)
          .filter(word ->
                  word.chars()
                  .mapToObj(c -> set.contains((char)c))
                  .reduce((a,b) -> a&&b)
                  .orElse(false)
                 )
          .count();
    }
Run Code Online (Sandbox Code Playgroud)

在此解决方案中,使用相同的逻辑,但allMatch我使用的map是 and then ,而不是reduce。从逻辑上讲,在单个false值来自map舞台后,reduce将始终评估为false。我知道 Java 流是惰性的,但我不确定它们什么时候“知道”它们到底有多惰性。这会比使用效率低吗allMatch,还是惰性会确保相同的操作?


最后,在这段代码中,我们可以看到 的值x将始终为 0,因为在仅过滤正数之后,它们的总和将始终为正(假设没有溢出),因此取正数的最小值和硬编码的 0 将为 0。流是否会足够懒惰,始终将其计算为 0,还是会减少过滤器之后的每个元素?

List<Integer> list = new ArrayList<>();
...
/*Some values added to list*/
...
int x = list.stream()
        .filter(i -> i >= 0)
        .reduce((a,b) -> Math.min(a+b, 0))
        .orElse(0);
Run Code Online (Sandbox Code Playgroud)

综上所述,我们如何知道 Java 流何时会出现惰性?我在代码中看到了懒惰的机会,但是我如何保证我的代码尽可能懒惰呢?

Hol*_*ger 5

您\xe2\x80\x99要求的实际术语是短路

\n
\n

此外,一些操作被视为短路操作。如果在提供无限输入时,中间操作可能会产生有限流,则该中间操作是短路的。如果当提供无限输入时,终端操作可以在有限时间内终止,则该终端操作是短路的。管道中的短路操作是无限流处理在有限时间内正常终止的必要条件,但不是充分条件。

\n
\n

术语 \xe2\x80\x9clazy\xe2\x80\x9d 仅适用于中间操作,意味着它们仅在终端操作请求时才执行工作。情况总是如此,因此当您不链接终端操作时,任何中间操作都不会处理任何元素。

\n

找出终端操作是否短路是相当容易的。转到APIStream文档并检查特定终端操作\xe2\x80\x99s文档是否包含这句话

\n
\n

这是短路端子操作。

\n
\n

allMatch有的reduce没有的

\n

这并不意味着这种基于逻辑或代数的优化是不可能的。但责任在于 JVM\xe2\x80\x99s 优化器,它可能会对循环执行相同的操作。然而,这需要内联所有涉及的方法,以确保该条件始终适用并且没有必须保留的副作用。这种行为兼容性意味着即使处理得到优化,apeek(System.out::println)也会继续打印所有元素,就像它们已被处理一样。在实践中,您不应该期望这样的优化,因为 Stream 实现代码对于优化器来说太复杂了。

\n

  • 这正是我在答案中所写的。“reduce”*不是*短路操作,文档告诉您这一点。 (3认同)