Вызов .map () для бесконечного потока? - PullRequest
0 голосов
/ 25 июня 2018

Согласно Javadocs для SE 8 Stream.map () выполняет следующие действия:

Возвращает поток, состоящий из результатов применения данной функции к элементам этого потока.

Однако в книге, которую я читаю ( Обучение сетевому программированию на Java , Ричард М. Риз), по сети реализован примерно следующий фрагмент кода на эхо-сервере.

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 знает, что поток бесконечен, поэтому он лениво выполняется, когда становятся доступны новые жетоны потока? Кажется, что добавление чего-либо в коллекцию, которая в настоящее время перебирается с помощью карты, является маленькой черной магией. Может ли кто-нибудь помочь мне понять, что происходит за кулисами?


Вот как я это повторил, чтобы избежать путаницы при использовании карты. Я считаю, что автор пытался избежать бесконечного цикла, так как вы не можете выйти из 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;
});

Ответы [ 3 ]

0 голосов
/ 25 июня 2018

Вот два наиболее важных предложения из документации .Предоставленный вами фрагмент - прекрасный пример того, как они работают вместе:

  • Stream::generate(Supplier<T> s) говорит, что возвращает:

    Возвращаетбесконечный последовательный неупорядоченный поток, где каждый элемент генерируется с помощью предоставленной Supplier.

  • 3-я точка документации пакета Stream:

    Лень ищущий.Многие потоковые операции, такие как фильтрация, отображение или удаление дубликатов, могут быть реализованы лениво, открывая возможности для оптимизации.Например, «найти первую строку с тремя последовательными гласными» не нужно проверять все входные строки.Потоковые операции подразделяются на промежуточные (генерирующие поток) и терминальные (создающие побочные эффекты).Промежуточные операции всегда ленивы.

В ярлыке этот сгенерированный поток ожидает дальнейших элементов, пока не будет достигнута операция терминала.Пока выполнение внутри предоставленного Supplier<T>, конвейер потока продолжается.

Например, если вы предоставите следующее Supplier, выполнение не сможет остановиться ибудет продолжаться бесконечно:

Supplier<String> inputLine = () -> {
    return "Hello world";
};
0 голосов
/ 25 июня 2018

Потоки ленивы.Думайте о них как о рабочих в цепи, которые передают друг другу ведра.Лень в том, что они будут просить рабочего позади них только за следующее ведро, если рабочий перед ними просит их об этом.

Так что лучше думать об этом как allMatch - бытьпоследнее действие, таким образом жаждущее - запросить поток map о следующем элементе, поток map запросить поток generate о следующем элементе и поток generate, идущий к его поставщику, и предоставить этот элементкак только он прибудет.

Останавливается, когда allMatch перестает запрашивать предметы.И делает это, когда знает ответ.Все элементы в этом потоке не нулевые?Как только allMatch получает элемент, который является нулевым, он знает, что ответ false, и завершит работу и не будет запрашивать больше элементов.Поскольку поток бесконечен, он не остановится в противном случае.

Таким образом, у вас есть два фактора, заставляющие это работать так, как он работает - один allMatch с нетерпением запрашивает следующий элемент (до тех пор, как предыдущиене был нулевым), и поток generate, который - для предоставления следующего элемента - возможно, должен был ждать поставщика, ожидающего, пока пользователь отправит больше ввода.

Но следует сказать,что map не следовало использовать здесь.В map не должно быть побочных эффектов - его следует использовать для отображения элемента одного типа на элемент другого типа.Я думаю, что этот пример использовался только в качестве учебного пособия.Гораздо проще и проще было бы использовать метод BufferedReader lines(), который дает вам конечное Stream строк, поступающих из буферизованного читателя.

0 голосов
/ 25 июня 2018

Да - Stream s настраиваются лениво до тех пор, пока вы не выполните терминальную операцию (последнее действие) на Stream. Или проще:

Пока операции вашего потока возвращают другой поток, у вас нет терминальной операции, и вы продолжаете цепочку, пока не получите что-то, возвращающее что-либо, кроме потока, включая void.

Это имеет смысл, так как для того, чтобы иметь возможность возвращать что-либо, кроме потока, операции, которые выполняются ранее в вашем потоке, необходимо будет оценить, чтобы реально предоставить данные.

В этом случае, как и в документации, allMatch возвращает boolean, и, следовательно, для вычисления этого логического значения требуется окончательное выполнение вашего потока. Это также точка, где вы предоставляете Predicate, ограничивающий ваш полученный Stream.

Также обратите внимание, что в документации указано:

Это операция короткого замыкания терминала .

Перейдите по этой ссылке для получения дополнительной информации об этих терминальных операциях, но терминальная операция в основном означает, что она фактически выполнит операцию. Кроме того, ограничением вашего бесконечного потока является «короткозамкнутый» аспект этого метода.

...