2014-11-01 3 views
4

Недавно я узнал о Stream с в Java 8 и увидел этот пример:Понимание Java 8 записи потока

IntStream stream = IntStream.range(1, 20); 

Теперь, давайте скажем, что мы хотим, чтобы найти первое число, которое divisable как на 3 и 5 . Мы, вероятно, filter дважды и findFirst следующим образом:

OptionalInt result = stream.filter(x -> x % 3 == 0) 
           .filter(x -> x % 5 == 0) 
           .findFirst(); 

Это все звучит довольно разумно. Удивительное, когда я пытался сделать это:

OptionalInt result = stream.filter(x -> {System.out.println(x); return x % 3 == 0;}) 
          .filter(x -> {System.out.println(x); return x % 5 == 0;}) 
          .findFirst(); 

System.out.println(result.getAsInt()); 

я ожидал получить что-то вроде: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20, а затем: 3 6 9 12 15 18. Потому что мы первые итерации по всем номерам от 1 до 20, отфильтровать только те, которые divisable на 3, а затем перебирать этот новый Stream и найти те, которые divisable на 5.

Но вместо этого я получил этот результат: 1 2 3 3 4 5 6 6 7 8 9 9 10 11 12 12 13 14 15 15 15

Похоже, что это не соответствует всем номерам. Более того, похоже, что он проверяет x % 5 == 0 только на те числа, которые делятся на 3.

Я не понимаю, почему он не перебирает все числа.

Вот онлайн фрагмент кода: http://www.tryjava8.com/app/snippets/5454a7f2e4b070922a64002b

ответ

7

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

Это означает, что когда вы сделали IntStream stream = IntStream.range(1, 20);, вы фактически не создали коллекцию с 20 элементами. Вы создали динамически вычисленную коллекцию. Каждый вызов этого потока next будет вычислять следующий элемент. Остальные предметы все еще «не там» (вроде говоря).

То же самое касается фильтра.

Когда вы добавляете фильтр, проверяющий деление на 3, вы получите новый поток, объединенный из двух вычислений - первый возвращает числа от 1 до 20, второе вычисление возвращает числа, разделенные 3. Важно понимать, что каждый раз вычисляется только первый элемент. Поэтому, когда вы добавили проверку на деление на 5, она работала только на те элементы, которые были делимы на 3. То же самое касается того, почему печать остановилась на 15. Метод findFirst возвращает первое число, которое передает все 3 вычисления (1- 20, деление на 3 вычисления и деление на 5 вычислений).

3

A Stream - это ленивый механизм оценки для эффективной обработки коллекций. Это означает, что все промежуточные операции в потоке не оцениваются, если это не необходимо для окончательной (терминальной) операции.

В вашем примере операция терминала firstFirst(). Это означает, что Stream будет оценивать конвейер промежуточных операций до тех пор, пока не найдет один int, который будет результатом передачи входного потока через все промежуточные операции.

Второй фильтр принимает только те, которые проходят первый фильтр, поэтому он обрабатывает только числа 3,6,9,12,15, а затем останавливается, так как 15 передает фильтр и поставляет операцию findFirst() единственным выходом необходимо.

Первый фильтр будет обрабатывать только данные входного потока, если операция терминала по-прежнему требует данных, и поэтому будет обрабатываться только с 1 по 15.