Según la documentación en el sitio web de Oracle [...]
Ese enlace es para Java 8. Es posible que desee leer la documentación para Java 9 (que salió en 2017) y versiones posteriores, ya que son más explícitos a este respecto. Específicamente:
Se permite una implementación de flujo latitud significativa en la optimización del cálculo del resultado. Por ejemplo, una implementación de flujo es libre de eludir operaciones (o etapas completas) de una tubería de flujo, y por lo tanto, elude la invocación de parámetros de comportamiento, si puede probar que no afectaría el resultado del cálculo. Esto significa que los efectos secundarios de los parámetros de comportamiento no siempre se pueden ejecutar y no se debe confiar en ellos, a menos que se especifique lo contrario (como por ejemplo las operaciones del terminal forEach
y forEachOrdered
). (Para ver un ejemplo específico de dicha optimización, consulte la nota API documentada sobre la count()
operación. Para obtener más detalles, consulte la sección de efectos secundarios de la documentación del paquete de transmisión).
Fuente: Javadoc de Java 9 para la Stream
interfaz .
Y también la versión actualizada del documento que citó:
Efectos secundarios
En general, se desaconsejan los efectos secundarios en los parámetros de comportamiento para transmitir las operaciones, ya que a menudo pueden conducir a violaciones involuntarias del requisito de apatridia, así como a otros riesgos de seguridad de hilos.
Si los parámetros de comportamiento tienen efectos secundarios, a menos que se indique explícitamente, no hay garantías en cuanto a :
- la visibilidad de esos efectos secundarios a otros hilos;
- que diferentes operaciones en el "mismo" elemento dentro de la misma tubería de flujo se ejecutan en el mismo hilo; y
- que los parámetros de comportamiento siempre se invocan, ya que una implementación de flujo es libre de eludir operaciones (o etapas completas) de una tubería de flujo si puede probar que no afectaría el resultado del cálculo.
El orden de los efectos secundarios puede ser sorprendente. Incluso cuando una tubería está restringida a producir un resultado que sea consistente con el orden de encuentro de la fuente de flujo (por ejemplo, IntStream.range(0,5).parallel().map(x -> x*2).toArray()
debe producir [0, 2, 4, 6, 8]
), no se garantiza el orden en que se aplica la función de mapeador a elementos individuales, o en qué hilo se ejecuta cualquier parámetro de comportamiento para un elemento dado.
La elusión de los efectos secundarios también puede ser sorprendente. Con la excepción de las operaciones de terminal forEach
yforEachOrdered
, los efectos secundarios de los parámetros de comportamiento pueden no siempre ejecutarse cuando la implementación del flujo puede optimizar la ejecución de los parámetros de comportamiento sin afectar el resultado del cálculo. (Para ver un ejemplo específico, consulte la nota API documentada sobre la count
operación).
Fuente: Javadoc de Java 9 para el java.util.stream
paquete .
Todo el énfasis es mío.
Como puede ver, la documentación oficial actual entra en más detalles sobre los problemas que puede encontrar si decide usar efectos secundarios en sus operaciones de transmisión. También es muy claro forEach
y forEachOrdered
es la única operación de terminal en la que se garantiza la ejecución de efectos secundarios (tenga en cuenta que los problemas de seguridad de subprocesos aún se aplican, como lo muestran los ejemplos oficiales).
Dicho esto, y con respecto a su código específico, y solo dicho código:
public List<SavedCars> saveCars(List<Car> cars) {
return cars.stream()
.map(this::saveCar)
.collect(Collectors.toList());
}
No veo problemas relacionados con Streams con dicho código tal como está.
- El
.map()
paso se ejecutará porque .collect()
(una operación de reducción mutable , que es lo que recomienda el documento oficial en lugar de cosas como .forEach(list::add)
) se basa en .map()
la salida y, dado que esta (es decir saveCar()
, la salida) es diferente de su entrada, el flujo no puede "probar que [elidándose] no afectaría el resultado del cálculo " .
- No es un problema,
parallelStream()
por lo que no debería presentar ningún problema de concurrencia que no existiera anteriormente (por supuesto, si alguien agregó .parallel()
más tarde, entonces pueden surgir problemas, como si alguien decidiera paralelizar un for
bucle activando nuevos hilos para los cálculos internos )
Eso no significa que el código en ese ejemplo sea Good Code ™. ¿La secuencia .stream.map(::someSideEffect()).collect()
como una forma de realizar operaciones de efectos secundarios para cada elemento de una colección puede parecer más simple / corta / elegante? que su for
contraparte, y a veces puede ser. Sin embargo, como Eugene, Holger y algunos otros le dijeron, hay mejores maneras de abordar esto.
Como una idea rápida: el costo de activar un Stream
vs iterar un simple for
no es insignificante a menos que tenga muchos artículos, y si tiene muchos artículos, entonces: a) probablemente no quiera hacer un nuevo acceso a la base de datos para cada uno, entonces una saveAll(List items)
API sería mejor; y b) probablemente no quieren tomar el impacto en el rendimiento del procesamiento de una gran cantidad de elementos de forma secuencial, por lo que terminaría usando la paralelización y luego surgiría un nuevo conjunto de problemas.