Что это означает, что промежуточные операции выполняются лениво, тогда как терминальные операции активно выполняются в API Java Stream? - PullRequest
0 голосов
/ 22 октября 2018
list.stream().filter( a-> a < 20 && a > 7).forEach(a -> System.out.println(a));

fiter выполняется лениво.

forEach выполняется с нетерпением.

Что это значит?

Ответы [ 4 ]

0 голосов
/ 22 октября 2018

Лениво выполнено означает, что операция будет выполнена только при необходимости .

С готовностью выполнено означает, что операции будут выполнены немедленно.

Итак, когда выполняются ленивые промежуточные операции, вы можете спросить?

Когда к конвейеру применяется терминальная операция (операция Eager).

Итак, как можномы знаем, является ли операция промежуточной (ленивой) или терминальной (нетерпеливой)?

Когда операция возвращает Stream<T>, где T может быть любого типа, тогда это промежуточная операция (ленивая);если операция возвращает что-либо еще, например, void, int, boolean и т. д., то это терминальная (нетерпеливая) операция.

0 голосов
/ 22 октября 2018

JavaDoc из Stream говорит:

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

JavaDoc о промежуточных операциях:

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

Поскольку map является ленивой операцией, следующий код ничего не напечатает:

Stream.of(1, 2, 3).map(i -> {
    System.out.println(i);
    return i;
});

В этом Stream отсутствует терминальная операция, которая выполняла бы ее, что вызвало бы промежуточные операции.

Аналог list.stream().filter( a-> a > 20 && a < 7) вернет Stream, но ни один элемент из list не был отфильтрован.

Но даже если выполняется терминальная операция, есть еще о лени:

Лень также позволяет избежать проверки всех данных, когда в этом нет необходимости;для таких операций, как «, найдите первую строку длиной более 1000 символов »

Ленивые операции выполняются, если их выполнение необходимо для определения результата Stream.И не все элементы из источника должны обрабатываться ленивой операцией.

JavaDoc для терминальных операций:

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

Кроме того, только одна операция терминала может быть применена к Stream.

После выполнения операции терминала потоковый конвейер считается использованным и больше не может использоваться;

Продолжение с примером:

long count = Stream.of(1, 2, 3).map(i -> {
    System.out.println(i);
    return i;
}).count();

Чтобы определить count, отображение не имеет значения.Таким образом, этот код по-прежнему ничего не печатает.Но поскольку count() является терминальной операцией, поток обрабатывается, и count получает присвоенное значение 3.

Если мы изменим терминальную операцию на .min(Comparator.naturalOrder());, то все сопоставления будут выполнены, и мы увидимцелые числа напечатаны.

0 голосов
/ 22 октября 2018

Скажем, у вас была следующая операция.

list.stream()
    .map(a -> a * a)
    .filter(a -> a > 0 && a < 100)
    .map(a -> -a)
    .forEach(a -> System.out.println(a));

Промежуточными операциями являются карты и фильтры, терминальной операцией является forEach.Если промежуточные операции были выполнены с нетерпением, то .map(a -> a * a) немедленно отобразит весь поток, и результат будет передан в .filter(a -> a > 0 && a < 10), который немедленно отфильтрует результат, который затем будет передан в .map(a -> -a), который отобразит отфильтрованный результат изатем передайте его в forEach, который немедленно напечатает каждый элемент из потока.

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

list.stream()
    .map(a -> a * a)
    .filter(a -> a > 0 && a < 100)
    .map(a -> -a)

фактически ничего не делает сразу.Он просто создает новый поток, который запоминает операции, которые он должен был выполнить, но на самом деле не выполняет их, пока не пришло время действительно произвести результат.Лишь после того, как forEach попытается прочитать значение из потока, он затем перейдет в исходный поток, примет значение, отобразит его, используя a -> a * a, отфильтрует его, и, если он пройдет фильтр, отобразит его, используя a -> -a и затем передает это значение в forEach.

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

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

Так в чем же преимущество?

Что ж, одним из основных преимуществ является то, что ленивый подход значительно улучшает время ожидания.Как вы, вероятно, знаете, один поток программы может делать только одну вещь за раз.Продолжим аналогию немного дальше, представьте, что там около 800 тарелок, но повар фактически должен был ждать, пока стиральная машина закончит мытье посуды, а затем вручить ему одну.Если энергичная стиральная машина настаивала на том, чтобы сначала вымыть все тарелки, прежде чем что-либо сдавать, повар должен был подождать, пока все 800 тарелок будут вымыты, а затем подать 800 блюд за раз, после чего все рассерженные клиенты ушли бы.*

Тем не менее, с ленивой шайбой, на каждый прием пищи, который хочет подать повар, ему остается только ждать одну тарелку.Таким образом, если мытье тарелки занимает 10 секунд, а подача происходит почти мгновенно, в сценарии 1 все блюда подаются сразу, но только после ожидания более двух часов.Но в сценарии 2 каждое блюдо подается с интервалом около 10 секунд.Поэтому, несмотря на то, что для подачи всех блюд требуется одинаковое количество времени, сценарий 2, безусловно, более желателен.

Здесь я немного упростил аналогию, но, надеюсь, это поможет вам лучше ее понять.

0 голосов
/ 22 октября 2018

Это означает, что list.stream().filter( a-> a > 20 && a < 7) не начнет выполняться, пока к потоку не будет применена терминальная операция (например, forEach(a -> System.out.println(a))).

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...