titledborder studio programacion poner móviles desarrollo definicion curso borde aplicaciones java java-8 java-stream peek

studio - En las secuencias de Java, ¿el vistazo es realmente solo para depuración?



set border java (6)

Estoy leyendo sobre las secuencias de Java y descubriendo cosas nuevas a medida que avanzo. Una de las cosas nuevas que encontré fue la función peek() . Casi todo lo que he leído en el vistazo dice que debería usarse para depurar sus Streams.

¿Qué pasaría si tuviera una transmisión en la que cada cuenta tenga un nombre de usuario, un campo de contraseña y un método de inicio de sesión () e inicio de sesión ()?

tambien tengo

Consumer<Account> login = account -> account.login();

y

Predicate<Account> loggedIn = account -> account.loggedIn();

¿Por qué sería esto tan malo?

List<Account> accounts; //assume it''s been setup List<Account> loggedInAccount = accounts.stream() .peek(login) .filter(loggedIn) .collect(Collectors.toList());

Ahora, por lo que puedo decir, esto hace exactamente lo que está destinado a hacer. Eso;

  • Toma una lista de cuentas
  • Intenta iniciar sesión en cada cuenta
  • Filtra cualquier cuenta que no haya iniciado sesión
  • Recopila las cuentas registradas en una nueva lista

¿Cuál es la desventaja de hacer algo como esto? ¿Alguna razón por la que no debería proceder? Por último, si no esta solución, ¿entonces qué?

La versión original de esto utilizó el método .filter () de la siguiente manera;

.filter(account -> { account.login(); return account.loggedIn(); })


Aunque estoy de acuerdo con la mayoría de las respuestas anteriores, tengo un caso en el que usar peek en realidad parece ser el camino más limpio.

Similar a su caso de uso, suponga que desea filtrar solo en cuentas activas y luego iniciar sesión en estas cuentas.

accounts.stream() .filter(Account::isActive) .peek(login) .collect(Collectors.toList());

Peek es útil para evitar la llamada redundante sin tener que repetir la colección dos veces:

accounts.stream() .filter(Account::isActive) .map(account -> { account.login(); return account; }) .collect(Collectors.toList());


Diría que peek proporciona la capacidad de descentralizar código que puede mutar objetos de flujo, o modificar el estado global (basado en ellos), en lugar de meter todo en una función simple o compuesta que se pasa a un método terminal.

Ahora la pregunta podría ser: ¿deberíamos mutar objetos de flujo o cambiar el estado global desde dentro de las funciones en la programación de Java de estilo funcional ?

Si la respuesta a cualquiera de las 2 preguntas anteriores es sí (o: en algunos casos sí), peek() definitivamente no es solo para propósitos de depuración , por la misma razón que forEach() no es solo para propósitos de depuración .

Para mí, cuando forEach() entre forEach() y peek() , es elegir lo siguiente: ¿Quiero fragmentos de código que muten los objetos de la secuencia para que se adjunten a una composición, o quiero que se adjunten directamente a la secuencia?

Creo que peek() se emparejará mejor con los métodos java9. por ejemplo, takeWhile() puede necesitar decidir cuándo detener la iteración en función de un objeto ya mutado, por lo que forEach() con forEach() no tendría el mismo efecto.

PD : no he hecho referencia a map() ninguna parte porque en caso de que queramos mutar objetos (o estado global), en lugar de generar nuevos objetos, funciona exactamente como peek() .


El punto clave de esto:

No use la API de forma no intencionada, incluso si cumple su objetivo inmediato. Ese enfoque puede romperse en el futuro, y tampoco está claro para los futuros mantenedores.

No hay daño en dividir esto en múltiples operaciones, ya que son operaciones distintas. Es perjudicial usar la API de una manera poco clara e involuntaria, lo que puede tener ramificaciones si este comportamiento particular se modifica en futuras versiones de Java.

El uso de forEach en esta operación dejaría en claro para el mantenedor que hay un efecto secundario previsto en cada elemento de las accounts y que está realizando alguna operación que puede mutarlo.

También es más convencional en el sentido de que peek es una operación intermedia que no opera en toda la colección hasta que se ejecuta la operación del terminal, pero para cada forEach es de hecho una operación de terminal. De esta manera, puede hacer argumentos sólidos sobre el comportamiento y el flujo de su código en lugar de hacer preguntas sobre si el peek se comportaría de la misma manera que forEach en este contexto.

accounts.forEach(a -> a.login()); List<Account> loggedInAccounts = accounts.stream() .filter(Account::loggedIn) .collect(Collectors.toList());


La solución funcional es hacer que el objeto de la cuenta sea inmutable. Por lo tanto, account.login () debe devolver un nuevo objeto de cuenta. Esto significará que la operación de mapa se puede usar para iniciar sesión en lugar de echar un vistazo.


Lo importante que debe comprender es que las transmisiones son controladas por la operación del terminal . La operación del terminal determina si todos los elementos tienen que ser procesados ​​o alguno. Por lo tanto, collect es una operación que procesa cada elemento, mientras que findAny puede dejar de procesar elementos una vez que encuentra un elemento coincidente.

Y count() puede no procesar ningún elemento en absoluto cuando puede determinar el tamaño de la secuencia sin procesar los elementos. Dado que esta es una optimización no realizada en Java 8, pero que sí lo estará en Java 9, puede haber sorpresas cuando cambie a Java 9 y el código dependa de count() procesa todos los elementos. Esto también está conectado a otros detalles dependientes de la implementación, por ejemplo, incluso en Java 9, la implementación de referencia no podrá predecir el tamaño de una fuente de flujo infinito combinada con un limit mientras que no haya una limitación fundamental que impida dicha predicción.

Dado que peek permite "realizar la acción proporcionada en cada elemento a medida que los elementos se consumen de la secuencia resultante ", no exige el procesamiento de elementos, pero realizará la acción dependiendo de lo que necesite la operación del terminal. Esto implica que debe usarlo con mucho cuidado si necesita un procesamiento en particular, por ejemplo, si desea aplicar una acción en todos los elementos. Funciona si se garantiza que la operación del terminal procesará todos los elementos, pero aun así, debe asegurarse de que no el próximo desarrollador cambie la operación del terminal (u olvide ese aspecto sutil).

Además, si bien las transmisiones garantizan el mantenimiento del orden de encuentro para cierta combinación de operaciones, incluso para transmisiones paralelas, estas garantías no se aplican al peek . Al recopilar en una lista, la lista resultante tendrá el orden correcto para las secuencias paralelas ordenadas, pero la acción de vista peek puede invocarse en un orden arbitrario y al mismo tiempo.

Entonces, lo más útil que puede hacer con peek es averiguar si se ha procesado un elemento de flujo, que es exactamente lo que dice la documentación de la API:

Este método existe principalmente para admitir la depuración, donde desea ver los elementos a medida que fluyen más allá de cierto punto en una tubería


Tal vez una regla general debería ser que si usa un vistazo fuera del escenario de "depuración", solo debe hacerlo si está seguro de cuáles son las condiciones de filtrado final e intermedio. Por ejemplo:

return list.stream().map(foo->foo.getBar()) .peek(bar->bar.publish("HELLO")) .collect(Collectors.toList());

parece ser un caso válido donde lo desea, en una operación para transformar todos los Foos en Bars y decirles a todos hola.

Parece más eficiente y elegante que algo como:

List<Bar> bars = list.stream().map(foo->foo.getBar()).collect(Collectors.toList()); bars.forEach(bar->bar.publish("HELLO")); return bars;

y no terminas iterando una colección dos veces.