¿Por qué este java Stream opera dos veces?

La API de Java 8 dice:

El recorrido de la fuente de la tubería no comienza hasta que se ejecuta la operación del terminal de la tubería.

Entonces, ¿por qué arroja el siguiente código?

java.lang.IllegalStateException: la transmisión ya se ha operado o cerrado

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

La primera operación de filtrado de acuerdo con la API no debe operar en el Stream.

Esto sucede porque ignoras el valor de retorno del 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 devuelve una nueva Stream consta de los elementos de esta secuencia que coinciden con el predicado dado. Pero es importante notar que es un nuevo Stream. El anterior ha sido operado cuando se le agregó el filtro. Pero el nuevo no era.

Citando de Stream Javadoc:

Una secuencia debe ser operada (invocando una operación de flujo intermedio o terminal) solo una vez.

En este caso, filter es la operación intermedia que operaba en la instancia anterior de Stream.

Entonces este código funcionará bien:

 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 notó Brian Goetz, normalmente encadenas esas llamadas juntas:

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

filter() método filter() usa la secuencia y devuelve otra instancia de Stream que ignoras en tu ejemplo.

filter es una operación intermedia, pero no puede llamar al filtro dos veces en la misma instancia de transmisión

Tu código debe escribirse de la siguiente manera:

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

Como el filtro es una operación intermedia, “nada” se realiza al llamar a estos métodos. Todo el trabajo se procesa realmente al llamar al método forEach()

La documentación en Streams dice:

“Una secuencia debe ser operada (invocando una operación de flujo intermedio o terminal) solo una vez”.

De hecho, puede ver esto en el código fuente. Cuando llama al filtro, devuelve una nueva operación sin estado, pasando la instancia de canalización actual en el constructor ( this ):

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

La llamada al constructor termina llamando al constructor AbstractPipeline , que está configurado de esta manera:

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

La primera vez que llama al filtro en la fuente (línea 2), establece el valor booleano en verdadero. Como no reutiliza el valor de retorno proporcionado por el filtro, la segunda llamada al filtro (línea 3) detectará que la fuente de transmisión original (línea 1) ya se ha vinculado (debido a la primera llamada al filtro) y de ahí la excepción que obtener.

Este es un uso indebido de la stream que se detecta cuando adjunta más de un .fliter() a ella.

No dice que ha sido atravesado más de una vez, solo que “ya ha sido operado”