En Java, ¿cuáles son las ventajas de las transmisiones sobre los bucles? [cerrado]

133

Me lo preguntaron en una entrevista y no estoy convencido de haber dado la mejor respuesta que podría tener. Mencioné que puede hacer una búsqueda paralela y que los valores nulos se manejaron de alguna manera que no podía recordar. Ahora me doy cuenta de que estaba pensando en los opcionales. ¿Que me estoy perdiendo aqui? Afirman que es un código mejor o más conciso, pero no estoy seguro de estar de acuerdo.


Considerando cuán sucintamente fue respondida, parece que esta no era una pregunta demasiado amplia después de todo.


Si están haciendo esta pregunta en las entrevistas, y claramente lo están haciendo, ¿para qué serviría desglosarla además de hacer que sea más difícil encontrar una respuesta? Quiero decir, ¿qué estás buscando? Podría desglosar la pregunta y tener todas las subpreguntas respondidas, pero luego crear una pregunta principal con enlaces a todas las preguntas secundarias ... aunque parece bastante tonto. Mientras lo hacemos, dame un ejemplo de una pregunta menos amplia. No sé cómo hacer solo una parte de esta pregunta y aún así obtener una respuesta significativa. Podría hacer exactamente la misma pregunta de una manera diferente. Por ejemplo, podría preguntar "¿Para qué sirven las transmisiones?" o "¿Cuándo usaría una secuencia en lugar de un bucle for?" o "¿Por qué molestarse con transmisiones en lugar de bucles?" Sin embargo, todas estas son exactamente la misma pregunta.

... o se considera demasiado amplio porque alguien dio una respuesta multipunto realmente larga? Francamente, cualquiera que lo sepa podría hacer eso con prácticamente cualquier pregunta. Si usted es uno de los autores de la JVM, por ejemplo, probablemente podría hablar de bucles durante todo el día cuando la mayoría de nosotros no podría.

"Edite la pregunta para limitarla a un problema específico con suficientes detalles para identificar una respuesta adecuada. Evite hacer varias preguntas distintas a la vez. Consulte la página Cómo hacer para obtener ayuda para aclarar esta pregunta".

Como se señala a continuación, se ha dado una respuesta adecuada que demuestra que hay una y que es lo suficientemente fácil de proporcionar.

usuario447607
fuente
77
Esto está basado en mi opinión. Personalmente, prefiero las transmisiones porque hace que el código sea más legible. Permite escribir lo que quieras en lugar de cómo . Además, es totalmente rudo hacer cosas increíbles con frases ingeniosas.
Arnaud Denoyelle
19
¿Incluso si es una línea 30, una línea? No me gustan las cadenas largas.
user447607
1
Además, todo lo que busco aquí es la respuesta adecuada para una entrevista. Esta es la única "opinión" que importa.
user447607
1
Educativamente hablando, esta pregunta también me salvó un poco de degradación en una entrevista futura, @slim realmente lo logró, pero industrialmente hablando, también habla de cómo los lenguajes de programación de Microsoft construyeron sus carreras al estafar el lenguaje java, y finalmente Java se venga al rasgar fuera de la Expresión Lambda y las transmisiones de los oponentes, veamos qué hará Java sobre Structs and Unions en el futuro :)
ShayHaned
55
Tenga en cuenta que las transmisiones solo aprovechan una fracción de la potencia en la programación funcional: - /
Thorbjørn Ravn Andersen

Respuestas:

251

Es interesante que la pregunta de la entrevista pregunte sobre las ventajas, sin preguntar sobre las desventajas, ya que las hay.

Las transmisiones son un estilo más declarativo . O un estilo más expresivo . Puede considerarse mejor declarar su intención en el código, que describir cómo se hace:

 return people
     .filter( p -> p.age() < 19)
     .collect(toList());

... dice claramente que está filtrando elementos coincidentes de una lista, mientras que:

 List<Person> filtered = new ArrayList<>();
 for(Person p : people) {
     if(p.age() < 19) {
         filtered.add(p);
     }
 }
 return filtered;

Dice "Estoy haciendo un bucle". El propósito del ciclo está enterrado más profundamente en la lógica.

Las corrientes son a menudo más bajas . El mismo ejemplo muestra esto. Terser no siempre es mejor, pero si puedes ser conciso y expresivo al mismo tiempo, mucho mejor.

Las corrientes tienen una fuerte afinidad con las funciones . Java 8 presenta lambdas e interfaces funcionales, que abren toda una caja de juguetes de potentes técnicas. Las secuencias proporcionan la forma más conveniente y natural de aplicar funciones a secuencias de objetos.

Las corrientes alientan menos mutabilidad . Esto está relacionado con el aspecto de la programación funcional: el tipo de programas que escribe utilizando secuencias tiende a ser el tipo de programas en los que no modifica objetos.

Las corrientes fomentan un acoplamiento más suelto . El código de manejo de la transmisión no necesita conocer la fuente de la transmisión o su método de finalización eventual.

Las transmisiones pueden expresar sucintamente un comportamiento bastante sofisticado . Por ejemplo:

 stream.filter(myfilter).findFirst();

Puede parecer a primera vista como si filtrara todo el flujo, luego devuelve el primer elemento. Pero, de hecho, findFirst()impulsa toda la operación, por lo que se detiene de manera eficiente después de encontrar un artículo.

Las corrientes brindan margen para futuras ganancias de eficiencia . Algunas personas han realizado una evaluación comparativa y han descubierto que las secuencias de un solo subproceso de las Listmatrices o memorias en memoria pueden ser más lentas que el bucle equivalente. Esto es plausible porque hay más objetos y gastos generales en juego.

Pero las corrientes escalan. Además del soporte integrado de Java para operaciones de flujo en paralelo, hay algunas bibliotecas para reducir el mapa distribuido usando Streams como API, porque el modelo se ajusta.

Desventajas?

Rendimiento : un forbucle a través de una matriz es extremadamente liviano tanto en términos de almacenamiento dinámico como de uso de CPU. Si la velocidad bruta y el ahorro de memoria son una prioridad, el uso de una transmisión es peor.

Familiaridad . El mundo está lleno de programadores de procedimientos con experiencia, de muchos orígenes lingüísticos, para quienes los bucles son familiares y las transmisiones son novedosas. En algunos entornos, desea escribir código que sea familiar para ese tipo de persona.

Sobrecarga cognitiva . Debido a su naturaleza declarativa y al aumento de la abstracción de lo que sucede debajo, es posible que deba crear un nuevo modelo mental de cómo el código se relaciona con la ejecución. En realidad, solo necesita hacer esto cuando las cosas salen mal, o si necesita analizar profundamente el rendimiento o los errores sutiles. Cuando "simplemente funciona", simplemente funciona.

Los depuradores están mejorando, pero incluso ahora, cuando está pasando por el código de flujo en un depurador, puede ser un trabajo más duro que el bucle equivalente, porque un bucle simple está muy cerca de las variables y las ubicaciones de código con las que trabaja un depurador tradicional.

Delgado
fuente
44
Creo que sería justo aclarar que las cosas similares a las transmisiones se están volviendo mucho más comunes y ahora aparecen en muchos lenguajes de uso común que no están especialmente orientados a FP.
Casey
55
Teniendo en cuenta los pros y los contras enumerados aquí, creo que las transmisiones NO valen la pena para nada que no sean usos muy simples (poca lógica si / luego / de lo contrario, no hay muchas llamadas anidadas o lambdas, etc.), en partes de rendimiento no críticas
Henrik Kjus Alstad
1
@HenrikKjusAlstad Eso no es absolutamente lo que quise comunicar. Las transmisiones son maduras, potentes, expresivas y completamente apropiadas para el código de grado de producción.
delgado
Oh, no quise decir que no lo usaría en producción. Pero más bien, usaría de forma predeterminada bucles / ifs pasados ​​de moda, etc., en lugar de secuencias, especialmente si la secuencia resultante se vería compleja. Estoy seguro de que hay usos en los que una transmisión superará los bucles y si es claro, pero la mayoría de las veces, creo que está "atado", o, a veces, al revés. Por lo tanto, pondría peso en el argumento de la sobrecarga cognitiva para apegarse a las viejas formas.
Henrik Kjus Alstad
1
@lijepdam, pero aún tendría un código que dice "Estoy iterando sobre esta lista (vea dentro del bucle para averiguar por qué)", cuando "iterar sobre la lista" no es la intención principal del código.
delgado
16

Dejando de lado la diversión sintáctica, los Streams están diseñados para trabajar con conjuntos de datos potencialmente infinitamente grandes, mientras que los arrays, las Colecciones y casi todas las clases de Java SE que implementa Iterable están completamente en la memoria.

Una desventaja de un Stream es que los filtros, mapeos, etc., no pueden arrojar excepciones marcadas. Esto hace que Stream sea una mala elección para, por ejemplo, operaciones de E / S intermedias.

VGR
fuente
77
Por supuesto, también puedes recorrer fuentes infinitas.
delgado
Pero si los elementos para procesar persisten en una base de datos, ¿cómo se utilizan los flujos? Un desarrollador junior podría verse tentado a leerlos todos en una Colección solo para usar Streams. Y eso sería un desastre.
Lluis Martinez
2
@LluisMartinez, una buena biblioteca de cliente de DB devolverá algo como Stream<Row>, o sería posible escribir las propias Streamoperaciones de cursor de resultado de DB de ajuste de implementación.
delgado
Que Streams ignore silenciosamente las excepciones es un error que recientemente me picó. Poco intuitivo.
xxfelixxx
@xxfelixxx Las transmisiones no ignoran silenciosamente las excepciones. Intente ejecutar esto:Arrays.asList("test", null).stream().forEach(s -> System.out.println(s.length()));
VGR
8
  1. Se dio cuenta incorrectamente: las operaciones paralelas usan Streams, no Optionals.

  2. Puede definir métodos que funcionen con flujos: tomarlos como parámetros, devolverlos, etc. No puede definir un método que tome un bucle como parámetro. Esto permite una operación de transmisión complicada una vez y usarla muchas veces. Tenga en cuenta que Java tiene un inconveniente aquí: sus métodos deben llamarse en someMethod(stream)lugar de los propios de la secuencia stream.someMethod(), por lo que mezclarlos complica la lectura: intente ver el orden de las operaciones en

    myMethod2(myMethod(stream.transform(...)).filter(...))

    Muchos otros lenguajes (C #, Kotlin, Scala, etc.) permiten alguna forma de "métodos de extensión".

  3. Incluso cuando solo necesita operaciones secuenciales y no desea reutilizarlas, de modo que pueda usar secuencias o bucles, las operaciones simples en las secuencias pueden corresponder a cambios bastante complejos en los bucles.

Alexey Romanov
fuente
Explique 1. ¿No es la interfaz opcional el medio por el cual los nulos se manejan en cadenas? Con respecto a 3, eso tiene sentido porque con los filtros de cortocircuito, el método solo se invocará para casos específicos. Eficiente. Tiene sentido que pueda afirmar que su uso reduce la necesidad de escribir código adicional que deberá probarse, etc. Tras la revisión, no estoy seguro de lo que quiere decir con el caso secuencial en 2.
user447607
1. Optionales una alternativa a null, pero no tiene nada que ver con operaciones paralelas. A menos que "Ahora me doy cuenta de que estaba pensando en las opciones" en su pregunta, ¿solo se trata de nullmanejo?
Alexey Romanov
Cambié el orden de 2 y 3 y los expandí un poco.
Alexey Romanov
6

Realiza un bucle sobre una secuencia (matriz, colección, entrada, ...) porque desea aplicar alguna función a los elementos de la secuencia.

Los flujos le dan la capacidad de componer funciones en elementos de secuencia y le permiten implementar las funciones más comunes (por ejemplo, mapeo, filtrado, búsqueda, clasificación, recopilación, ...) independientemente de un caso concreto.

Por lo tanto, dada una tarea de bucle en la mayoría de los casos, puede expresarla con menos código usando Streams, es decir, gana legibilidad .

wero
fuente
44
Bueno, no se trata solo de legibilidad. El código que no tiene que escribir es un código que no tiene que probar.
user447607
3
parece que también está
obteniendo
6

Yo diría que su paralelización es tan fácil de usar. Intenta iterar sobre millones de entradas en paralelo con un bucle for. Vamos a muchos cpus, no más rápido; así que cuanto más fácil sea correr en paralelo, mejor, y con Streams esto es muy fácil.

Lo que me gusta mucho es la verbosidad que ofrecen. Toma poco tiempo entender lo que realmente hacen y producen en lugar de cómo lo hacen.

Eugene
fuente