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