在无限流上调用 .map()?

Invoking .map() on an infinite stream?

根据 JavaSE 8 的文档 Stream.map() 执行以下操作

Returns a stream consisting of the results of applying the given function to the elements of this stream.

但是,我正在阅读的一本书(Learning Network Programming with Java,Richard M. Reese)在回显中大致实现了以下代码片段服务器。

Supplier<String> inputLine = () -> {
    try {
        return br.readLine();
    } catch(IOException e) {
        e.printStackTrace();
        return null;
    }
};

Stream.generate(inputLine).map((msg) -> {
    System.out.println("Recieved: " + (msg == null ? "end of stream" : msg));
    out.println("echo: " + msg);
    return msg;
}).allMatch((msg) -> msg != null);

这应该是实现将用户输入打印到套接字输入流的功能方法。它按预期工作,但我不太明白如何。是不是因为 map 知道流是无限的,所以它会在新的流令牌可用时延迟执行?似乎向当前由 map 迭代的集合添加一些东西是一个小黑魔法。有人可以帮我了解幕后发生的事情吗?


以下是我重申这一点的方式,以避免混淆地图的使用。我相信作者试图避免无限循环,因为您无法跳出 forEach。

Stream.generate(inputLine).allMatch((msg) -> {
        boolean alive = msg != null;
        System.out.println("Recieved: " + (alive ? msg : "end of stream"));
        out.println("echo: " + msg);

        return alive;
});

是 - Stream 会延迟设置,除非您对 Stream 执行终端操作(最终操作)。或者更简单:

只要你的流上的操作 return 另一个流,你就没有终端操作,你一直在链接,直到你有东西 returning除 Stream 之外的任何内容,包括 void.

这是有道理的,为了能够 return 除了流之外的任何东西,需要评估流中较早的操作才能实际提供数据。

在这种情况下,根据文档,allMatch returns a boolean, and thus final execution of your stream is required to calculate that boolean. This is the point also where you provide a Predicate 限制了您的结果 Stream

另请注意,在文档中它指出:

This is a short-circuiting terminal operation.

关注 link 以获取有关这些终端操作的更多信息,但终端操作基本上意味着它将实际执行该操作。此外,无限流的限制是该方法的 'short-circuiting' 方面。

这是 文档中最相关的两个句子。您提供的代码段是这些协同工作的完美示例:

  • Stream::generate(Supplier<T> s) 表示 returns:

    Returns an infinite sequential unordered stream where each element is generated by the provided Supplier.

  • Stream 包文档的
  • 3rd dot

    Laziness-seeking. Many stream operations, such as filtering, mapping, or duplicate removal, can be implemented lazily, exposing opportunities for optimization. For example, "find the first String with three consecutive vowels" need not examine all the input strings. Stream operations are divided into intermediate (Stream-producing) operations and terminal (value- or side-effect-producing) operations. Intermediate operations are always lazy.

在快捷方式中,此生成的流等待进一步的元素,直到到达终端操作。只要在提供的 Supplier<T> 内执行,流管道就会继续。

例如,如果您提供以下内容Supplier,执行将没有机会停止并将无限继续:

Supplier<String> inputLine = () -> {
    return "Hello world";
};

流是惰性的。将它们视为链条中的工人,将桶相互传递。懒惰在于,如果他们前面的工人要求他们,他们只会向他们后面的工人要下一个桶。

所以最好将其视为 allMatch - 作为最终行动,因此急切 - 向 map 流询问下一个项目,而 map 流询问generate 流用于下一个项目,generate 流转到其供应商,并在该项目到达后立即提供该项目。

allMatch 停止索取物品时,它就会停止。当它知道答案时,它就会这样做。此流中的所有项目都不为空吗?一旦 allMatch 收到一个 null 项目,它就知道答案是 false,并且将完成并且不再要求任何项目。因为流是无限的,否则不会停止。

所以你有两个因素导致它以它的方式工作 - 一个是 allMatch 急切地询问下一个项目(只要之前的不为空),以及 generate 流 - 为了提供下一个项目 - 可能需要等待等待用户发送更多输入的供应商。

不过应该说map不应该用在这里。 map 中不应该有副作用 - 它应该用于将一种类型的项目映射到另一种类型的项目。我认为此示例仅用作学习辅助工具。更简单直接的方法是使用 BufferedReader's method lines(),它为您提供来自缓冲 reader.

的有限 Stream