在 findFirst() 之前使用 sorted() 进行流不再是懒惰的

2022-09-03 13:31:43

我有一个元素列表,我需要找到满足条件的第一个元素,然后使用Java 8流退出。

我认为下面的代码不幸地评估了所有可用的元素,这不是我需要的,我需要逐个评估项目,并在找到第一个匹配项时停止():break

我在这里对元素进行排序,然后将元素映射到其属性,然后尝试过滤,如果不是空或空,则查找匹配项!urlurlfirst

Arrays.stream(dataArray)
.sorted(Comparator.comparing(d -> d.getPriority()))
.peek(o -> System.out.println("SORT: " + o))
.map(d -> d.getOriginalURL(shortUrl))
.peek(o -> System.out.println("MAP: " + o))
.filter(u -> u != null && !u.isEmpty())
.peek(o -> System.out.println("FILTER: " + o))
.findFirst().orElse("");

但输出显示,即使第一个项目与条件 () 操作匹配,也会计算所有项目。iffilter

Data[] data = new Data[] { new ParseData(), new InMemoryData() };
System.out.println(">>> " + getOriginalURL(data, ""));

输出:

SORT: mhewedy.usingspark.data.InMemoryData@7adf9f5f
MAP: InMemory URL
FILTER: InMemory URL
SORT: mhewedy.usingspark.data.ParseData@85ede7b
MAP: Parse.com URL         <<< THIS SHOULD NOT HAPPEN
FILTER: Parse.com URL      <<< AND THIS TOO
>>> InMemory URL

如输出所示,当过滤器与第一个元素匹配时,流不会停止,而是继续计算第二个元素!

我想这样做:

Arrays.sort(dataArray, Comparator.comparing(d -> d.getPriority())); // sort

for (Data data : dataArray) {
    String url = data.getOriginalURL(shortUrl);           // map
    if (url != null && !url.isEmpty()) {                  // filter
        System.out.println("url :" + url);            
        return url;                                   // find first
    }
}

答案 1

下面是一个较小的示例来说明此问题:

Stream.of("a", "ab", "abc", "abcd")
    // .sorted() // uncomment and what follows becomes eager
    .filter(s -> s.contains("b"))
    .peek(s -> System.out.println("PEEK: " + s))
    .findFirst()
    .orElse("X");

不出所料,输出为:

PEEK: ab

如果该行未注释,则输出为:sorted

PEEK: ab
PEEK: abc
PEEK: abcd

(正如预期的那样,在这两种情况下,整个管道的最终结果都是“ab”。

确实,在生成其第一个输出元素之前,必须消耗其所有输入。从这个意义上说,它是渴望的。但是,它影响元素向下游发送的方式似乎很奇怪。sorted

如果不进行排序,该操作将从上游“拉取”元素,直到找到一个元素,然后停止。通过排序,操作会急切地收集所有元素,对它们进行排序,并且由于它在那里拥有它们,因此它会将它们“推”到流中。当然,忽略除第一个元素之外的所有内容。但这意味着干预操作(如过滤器)可能会执行不必要的工作。findFirstsorted()findFirst

最终结果是正确的,但行为是意外的。这可能被视为错误。如果合适,我将调查并提交错误。


答案 2

该操作强制遍历流中的所有项目。sorted

有状态操作(如 distinct 和 sorted)在处理新元素时可能会合并以前见过的元素的状态。

有状态操作可能需要在生成结果之前处理整个输入。例如,在看到流的所有元素之前,无法通过对流进行排序产生任何结果。

(来源)

但是,我不确定为什么后面的操作也对流中的所有元素执行。sorted

如果单独执行排序,然后将流用于其余的处理,则处理将在找到第一个匹配项时停止,如预期的那样。

Arrays.sort(dataArray, Comparator.comparing(d -> d.getPriority())); // sort

Arrays.stream(dataArray)
.peek(o -> System.out.println("SORT: " + o))
.map(d -> d.getOriginalURL(shortUrl))
.peek(o -> System.out.println("MAP: " + o))
.filter(u -> u != null && !u.isEmpty())
.peek(o -> System.out.println("FILTER: " + o))
.findFirst().orElse("");

推荐