Por que este stream java é operado duas vezes?

A API do Java 8 diz:

A transmissão da origem do pipeline não começa até que a operação do terminal do pipeline seja executada.

Então, por que o seguinte código lança:

java.lang.IllegalStateException: stream já foi operado ou fechado

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

A primeira operação de filtragem de acordo com a API não deve funcionar no stream.

Isso acontece porque você está ignorando o valor de retorno do filter .

 Stream 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 retorna um novo Stream consiste nos elementos desse stream que correspondem ao predicado fornecido. Mas é importante notar que é um novo stream. O antigo foi operado quando o filtro foi adicionado a ele. Mas o novo não foi.

Citando a partir do Stream Javadoc:

Um stream deve ser operado (invocando uma operação de stream intermediário ou terminal) apenas uma vez.

Nesse caso, filter é a operação intermediária que operou na antiga instância do Stream.

Então, esse código funcionará bem:

 Stream 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); 

Como observado por Brian Goetz, você normalmente encadeava essas chamadas:

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

filter() método filter() usa o stream e retorna uma outra instância do Stream que você ignora no seu exemplo.

filter é uma operação intermediária, mas você não pode chamar o filtro duas vezes na mesma instância de stream

Seu código deve ser escrito da seguinte forma:

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

Como o filtro é uma operação intermediária, “nada” é feito ao chamar esses methods. Todo o trabalho é realmente processado ao chamar o método forEach()

A documentação no Streams diz:

“Um stream deve ser operado (invocando uma operação de stream intermediário ou terminal) apenas uma vez.”

Você pode realmente ver isso no código-fonte. Quando você chama filter, ele retorna uma nova operação stateless, passando a instância atual do pipeline no construtor ( this ):

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

A chamada do construtor acaba chamando o construtor AbstractPipeline , que é configurado assim:

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

A primeira vez que você chama o filtro na origem (linha 2), ele define o valor booleano como true. Como você não reutiliza o valor de retorno dado pelo filtro, a segunda chamada a filtrar (linha 3) detectará que a origem do stream original (linha 1) já foi vinculada (devido à primeira chamada de filtro) e, portanto, a exceção obter.

Este é um uso indevido do stream que é detectado quando você anexa mais de um .fliter() a ele.

Não diz que foi percorrido mais de uma vez, apenas que “já foi operado”