Бесконечный поток и фильтр

Я новичок в Java 8 Stream API и на самом деле не понимаю, почему мой код не работает:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Stream.iterate(0, x -> x+3)
                                        .filter(x -> x>10 && x<100).peek(System.out::println)
                                        .collect(Collectors.toList());
        numbers.forEach(System.out::println);
    }
}

Как я понимаю "лень" потоков я написал:

  1. Создать поток с числами, кратными 3

  2. отфильтруйте его и дайте мне поток чисел из диапазона (10, 100)

  3. собрать этот поток в список

Как я вижу, есть некоторая проблема с бесконечным циклом, поэтому peek() печатает число из диапазона (12, 99), что нормально, но после этого снова печатает числа из (11, 98) и т. д. Не могли бы вы объяснить, где я сделал ошибка?


person kirx99    schedule 06.08.2014    source источник


Ответы (1)


Ни компилятор, ни среда выполнения не знают, что filter отфильтрует все числа, превышающие 100. Поэтому среда выполнения продолжает генерировать бесконечные целые числа и применять к ним фильтр.

У вас есть несколько способов решить эту проблему:

Используйте limit, чтобы усечь бесконечный поток до конечного потока. Это делает следующий фильтр немного ненужным (только тест x>10 все еще будет актуален, если вы установите жесткое ограничение).

public static void main(String[] args) {
    List<Integer> numbers = Stream.iterate(0, x -> x+3)
                                    .limit(34)
                                    .filter(x -> x>10 && x<100).peek(System.out::println)
                                    .collect(Collectors.toList());
    numbers.forEach(System.out::println);
}

Используйте IntStream.range и умножьте на 3 :

public static void main(String[] args) {
    List<Integer> numbers = IntStream.range(0, 34)
                                    .map(x -> 3*x)
                                    .collect(Collectors.toList());
    numbers.forEach(System.out::println);
}

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

Когда вы обрабатываете бесконечный поток, вы можете либо обрезать его до конечного потока (используя ограничение), либо иметь терминальную операцию, которая не должна обрабатывать все элементы потока (примеры: anyMatch, findFirst, findAny) .

person Eran    schedule 06.08.2014
comment
На самом деле я не понимаю, почему генерируются числа, не удовлетворяющие условию в filter(). Насколько мне известно, потоки в Java ленивы, поэтому должны генерироваться только числа, удовлетворяющие условию. Использование limit() не имеет смысла, потому что я подсчитал, сколько чисел, деленных на 3, находится в диапазоне от 10 до 100 (или в другом диапазоне), поэтому использование потоков не имеет смысла, потому что я должен знать коллекцию до ее фактического создания. - person kirx99; 06.08.2014
comment
И та же проблема с вашим вторым решением - потому что, когда я меняю диапазон от 1 до 98765, я не хочу подсчитывать, сколько чисел делится на 3 между ними - было бы лучше использовать диапазон (10, 100). filter(x -> x%3 == 0) кстати. Мой вопрос общий о том, почему в моем случае не работает ленивость Java-потоков. - person kirx99; 06.08.2014
comment
@ marekx99 Вы можете использовать более высокий предел, скажем, 100, и в этом случае вам все еще нужен фильтр (поскольку вы знаете, что фильтр не пропускает числа выше 99). Тот факт, что потоки ленивы, означает, что они начинают выполняться только тогда, когда сталкиваются с последней операцией. Если операция toList, вы запрашиваете поместить в список все целые числа, которые проходят фильтр, и чтобы узнать, какие целые числа проходят фильтр, Java должен применить фильтр к каждому целому числу в потоке (что является проблемой , так как их бесконечное количество). - person Eran; 06.08.2014
comment
@marekx99 marekx99 Я надеюсь, что мой комментарий прояснил, почему это так не работает. Когда вы обрабатываете бесконечный поток, вы можете либо обрезать его до конечного потока (используя ограничение), либо иметь терминальную операцию, которая не должна обрабатывать все элементы потока (примеры: anyMatch, findFirst, findAny) . - person Eran; 06.08.2014
comment
Я думаю, что наиболее важным ключевым словом здесь является терминальная операция, тогда как filter() является лишь промежуточной операцией. Спасибо за вашу помощь! - person kirx99; 06.08.2014
comment
@marekx99: и ваше второе упущение, о котором еще не упоминалось: диапазон значений int конечен, даже если ваш поток не конечен. Значения int будут переполнены, если вы не ограничите поток. Таким образом, вы можете использовать IntStream.range(0, Integer.MAX_VALUE) для решения всех ваших проблем (это не должно быть большой проблемой производительности, если оптимизатор точки доступа достаточно умен)… - person Holger; 06.08.2014
comment
Да, должна быть бесконечность, чтобы быть более бесконечной, например, должен использоваться BigInteger (но это, конечно, все еще не настоящая бесконечность :)). Я хотел бы использовать IntStream в моем случае, но, к сожалению, не могу - person kirx99; 06.08.2014