Estoy leyendo sobre las secuencias de Java y descubriendo cosas nuevas a medida que avanzo. Una de las cosas nuevas que encontré fue la peek()función. 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();
})
fuente

forEachpuede ser la operación que desea en lugar depeek. El hecho de que esté en la API no significa que no esté abierto al abuso (comoOptional.of)..peek(Account::login)y.filter(Account::loggedIn); no hay razón para escribir un consumidor y un predicado que simplemente llame a otro método como ese.forEach()ypeek(), solo puede operar a través de efectos secundarios; estos deben usarse con cuidado. ". Mi comentario fue más para recordar que lapeekoperación (que está diseñada para fines de depuración) no debe reemplazarse haciendo lo mismo dentro de otra operación comomap()ofilter().Respuestas:
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. No es malo en el uso de la API de una manera poco clara y no intencional, que puede tener consecuencias si este comportamiento particular, se modifica en futuras versiones de Java.
El uso
forEachen esta operación dejaría en claro para el mantenedor que hay un efecto secundario previsto en cada elemento deaccounts, y que está realizando alguna operación que puede mutarlo.También es más convencional en el sentido de que
peekes una operación intermedia que no opera en toda la colección hasta que se ejecuta la operación de la terminal, sino queforEaches una operación de la 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 sipeekse comportaría igualforEachque en este contexto.fuente
forEachdirectamente en la colección de origen:accounts.forEach(a -> a.login());login()método devolviera unbooleanvalor que indicara el estado de éxito ...login()devuelve aboolean, puede usarlo como un predicado, que es la solución más limpia. Todavía tiene un efecto secundario, pero está bien siempre y cuando no interfiera, es decir, elloginproceso 'de unoAccountno tiene influencia en el proceso' de inicio de sesión 'de otroAccount.Lo importante que debe comprender es que los flujos son controlados por la operación del terminal . La operación del terminal determina si todos los elementos tienen que ser procesados o alguno. Entonces,
collectes una operación que procesa cada elemento, mientras quefindAnypuede detener el procesamiento de elementos una vez que encuentra un elemento coincidente.Y es
count()posible que no procese ningún elemento cuando pueda 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 tenga un código que dependa delcount()procesamiento de 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 conlimitninguna limitación fundamental que impida dicha predicción.Ya que
peekpermite "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 incluso 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 mantener el orden de encuentro para una determinada combinación de operaciones incluso para transmisiones paralelas, estas garantías no se aplican a
peek. Al recopilar en una lista, la lista resultante tendrá el orden correcto para las secuencias paralelas ordenadas, pero lapeekacción puede invocarse en un orden arbitrario y al mismo tiempo.Entonces, lo más útil que puedes hacer con
peekes averiguar si se ha procesado un elemento de flujo, que es exactamente lo que dice la documentación de la API:fuente
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:
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:
y no terminas iterando una colección dos veces.
fuente
Diría que
peekproporciona la capacidad de descentralizar el código que puede mutar objetos de flujo, o modificar el estado global (basado en ellos), en lugar de incluir 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 anteriores el 2 preguntas es sí (o: en algunos casos sí) a continuación,
peek()se ofrecen sin duda no sólo para propósitos de depuración , por la misma razón queforEach()no es sólo para fines de depuración .Para mí, al elegir entre
forEach()ypeek(), elegir lo siguiente: ¿Quiero que se adjunten piezas de código que muten los objetos de la secuencia a una composición, o quiero que se adjunten directamente a la secuencia?Creo que
peek()se combinará mejor con los métodos java9. Por ejemplo, estakeWhile()posible que deba decidir cuándo detener la iteración en función de un objeto ya mutado, por lo que emparejarloforEach()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 igualpeek().fuente
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.
Peek es útil para evitar la llamada redundante sin tener que repetir la colección dos veces:
fuente
La solución funcional es hacer que el objeto de la cuenta sea inmutable. Entonces 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.
fuente