Когда используется Java 8 Stream, который считается потребляемым?

Я понял, что Java 8 Stream считается потребляемым после выполнения терминальной операции, например forEach() или count().

Однако тестовый пример multipleFilters_separate ниже вызывает IllegalStateException, хотя filter является ленивой промежуточной операцией, просто называемой как два оператора. И тем не менее, я могу связать две операции фильтра с одним оператором и работать.

@Test(expected=IllegalStateException.class)
public void multipleFilters_separate() {
    Stream<Double> ints = Stream.of(1.1, 2.2, 3.3);
    ints.filter(d -> d > 1.3);
    ints.filter(d -> d > 2.3).forEach(System.out::println);
}

@Test
public void multipleFilters_piped() {
    Stream<Double> ints = Stream.of(1.1, 2.2, 3.3);
    ints.filter(d -> d > 1.3)
        .filter(d -> d > 2.3)
        .forEach(System.out::println);
}

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

Ответы

Ответ 1

A Stream считается потребляемым после выполнения операции терминала. Однако даже несколько промежуточных операций не должны выполняться для одного и того же экземпляра Stream, как указано в Stream javadoc:

Поток должен быть включен (вызов операции промежуточного или конечного потока) только один раз. Это исключает, например, "разветвленные" потоки, где один и тот же источник передает два или более конвейера или несколько обходов одного и того же потока. Реализация потока может вызывать исключение IllegalStateException, если он обнаруживает, что поток повторно используется. Однако, поскольку некоторые потоковые операции могут возвращать их приемник, а не новый объект потока, может быть невозможно обнаружить повторное использование во всех случаев.

В случае операций промежуточного потока вы должны вызвать следующую операцию на Stream, возвращенную предыдущей операцией:

public void multipleFilters_separate() {
    Stream<Double> ints = Stream.of(1.1, 2.2, 3.3);
    ints = ints.filter(d -> d > 1.3);
    ints.filter(d -> d > 2.3).forEach(System.out::println);
}

Ответ 2

В соответствии с Stream Javadoc:

Поток должен работать (вызывать операцию промежуточного или терминального потока) только один раз. Это исключает, например, "разветвленные" потоки, где один и тот же источник передает два или более конвейера или несколько обходов одного и того же потока. Реализация потока может бросать IllegalStateException, если он обнаруживает, что поток повторно используется. Однако, поскольку некоторые потоковые операции могут возвращать их приемник, а не новый объект потока, возможно, не удастся обнаружить повторное использование во всех случаях.

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