Ответ 1
Когда вы делаете это:
int sum = IntStream.of(1, 2, 3, 4).map(i -> i + 1).sum();
Каждый цепочечный метод вызывается на возвращаемом значении предыдущего метода в цепочке.
Таким образом, map
вызывается для того, что IntStream.of(1, 2, 3, 4)
и sum
что возвращает map(i → я + 1)
.
Вам не нужно связывать потоковые методы, но они более читабельны и менее подвержены ошибкам, чем при использовании этого эквивалентного кода:
IntStream is = IntStream.of(1, 2, 3, 4);
is = is.map(i -> i + 1);
int sum = is.sum();
Что не совпадает с кодом, который вы указали в своем вопросе:
IntStream is = IntStream.of(1, 2, 3, 4);
is.map(i -> i + 1);
int sum = is.sum();
Как видите, вы игнорируете ссылку, возвращенную map
. Это причина ошибки.
РЕДАКТИРОВАТЬ (согласно комментариям, спасибо @IanKemp за указание на это): На самом деле, это является внешней причиной ошибки. Если вы перестанете думать об этом, map
должна что-то делать внутренне для самого потока, в противном случае, как тогда операция терминала вызовет преобразование, переданное в map
для каждого элемента? Я согласен с тем, что промежуточные операции ленивы, то есть при вызове они ничего не делают с элементами потока. Но внутренне они должны сконфигурировать некоторое состояние в самом конвейере потока, чтобы их можно было применить позже.
Несмотря на то, что я не знаю всех подробностей, происходит то, что концептуально map
выполняет как минимум 2 вещи:
-
Это создает и возвращает новый поток, который содержит функцию, переданную где-то в качестве аргумента, так что он может быть применен к элементам позже, когда вызывается терминальная операция.
-
Он также устанавливает флаг для старого экземпляра потока, то есть того, к которому он был вызван, указывая, что этот экземпляр потока больше не представляет действительное состояние для конвейера. Это связано с тем, что новое обновленное состояние, содержащее функцию, переданную в
map
, теперь инкапсулируется возвращаемым экземпляром. (Я полагаю, что это решение могло быть принято командой jdk, чтобы ошибки появлялись как можно раньше, то есть, вызывая раннее исключение вместо того, чтобы позволить конвейеру перейти в недопустимое/старое состояние, которое не удерживает функцию для применять, тем самым позволяя работе терминала возвращать неожиданные результаты).
Позже, когда терминальная операция вызывается для этого экземпляра, помеченного как недопустимая, вы получаете это IllegalStateException
. Два вышеуказанных элемента настраивают глубокую внутреннюю причину ошибки.
Другой способ увидеть все это - убедиться, что экземпляр Stream
работает только один раз, посредством промежуточной или терминальной операции. Здесь вы нарушаете это требование, потому что вы вызываете map
и sum
в одном и том же экземпляре.
На самом деле, в Javadocs для Stream
ясно сказано:
Поток должен использоваться (вызывая промежуточную или терминальную операцию потока) только один раз. Это исключает, например, "разветвленные" потоки, в которых один и тот же источник передает два или более конвейеров или несколько обходов одного и того же потока. Реализация потока может
IllegalStateException
если обнаружит, что поток используется повторно. Однако, поскольку некоторые потоковые операции могут возвращать своего приемника, а не новый объект потока, может оказаться невозможным обнаружить повторное использование во всех случаях.