Подтвердить что ты не робот

Почему этот Java-поток работает дважды?

API Java 8 говорит:

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

Итак, почему следующий код выдает:

java.lang.IllegalStateException: поток уже работает или закрыто

Stream<Integer> stream = Stream.of(1,2,3);
stream.filter( x-> x>1 );
stream.filter( x-> x>2 ).forEach(System.out::print);

Первая операция фильтрации в соответствии с API не должна работать в потоке.

4b9b3361

Ответ 1

Это происходит потому, что вы игнорируете возвращаемое значение filter.

Stream<Integer> stream = Stream.of(1,2,3);
stream.filter( x-> x>1 ); // <-- ignoring the return value here
stream.filter( x-> x>2 ).forEach(System.out::print);

Stream.filter возвращает новый Stream, состоящий из элементов этого потока, соответствующих данному предикату. Но важно отметить, что это новый поток. Старая была оперирована, когда фильтр был добавлен к ней. Но нового не было.

Цитата из Stream Javadoc:

Поток должен работать (только при вызове операции промежуточного или конечного потока).

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

Итак, этот код будет работать нормально:

Stream<Integer> stream = Stream.of(1,2,3);
stream = stream.filter( x-> x>1 ); // <-- NOT ignoring the return value here
stream.filter( x-> x>2 ).forEach(System.out::print);

Как отметил Брайан Гетц, вы обычно связываете эти вызовы:

Stream.of(1,2,3).filter( x-> x>1 )
                .filter( x-> x>2 )
                .forEach(System.out::print);

Ответ 2

filter() метод использует поток и возвращает еще один экземпляр Stream, который вы игнорируете в своем примере.

filter является промежуточной операцией, но вы не можете дважды вызывать фильтр в одном экземпляре потока

Ваш код должен записываться следующим образом:

Stream<Integer> stream = Stream.of(1,2,3);
                               .filter( x-> x>1 )
                               .filter( x-> x>2);
stream.forEach(System.out::print);

Поскольку фильтр является промежуточной операцией, "ничего" не выполняется при вызове методов theses. Вся работа действительно обрабатывается при вызове метода forEach()

Ответ 3

В документации по потокам говорится:

"Поток должен работать (вызывая операцию промежуточного или терминального потока) только один раз."

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

@Override
public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
    Objects.requireNonNull(predicate);
    return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
                                       StreamOpFlag.NOT_SIZED) {
        ....
}

Вызов конструктора заканчивается вызовом конструктора AbstractPipeline, который настроен следующим образом:

AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) {
    if (previousStage.linkedOrConsumed)
        throw new IllegalStateException(MSG_STREAM_LINKED);
    previousStage.linkedOrConsumed = true;
    ...
}

При первом вызове фильтра на источнике (строка 2) он устанавливает значение boolean равным true. Поскольку вы не повторно используете возвращаемое значение, заданное фильтром, второй вызов фильтра (строка 3) обнаружит, что исходный источник потока (строка 1) уже связан (из-за первого вызова фильтра), и, следовательно, исключение вы получить.

Ответ 4

Это неправильное использование stream, которое обнаруживается, когда вы прикрепляете к нему более одного .fliter().

Он не говорит, что он проходил более одного раза, только потому, что он "уже оперировался"