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
forEach
puede 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 lapeek
operació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
forEach
en 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
peek
es una operación intermedia que no opera en toda la colección hasta que se ejecuta la operación de la terminal, sino queforEach
es 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 sipeek
se comportaría igualforEach
que en este contexto.fuente
forEach
directamente en la colección de origen:accounts.forEach(a -> a.login());
login()
método devolviera unboolean
valor 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, ellogin
proceso 'de unoAccount
no 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,
collect
es una operación que procesa cada elemento, mientras quefindAny
puede 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 conlimit
ninguna limitación fundamental que impida dicha predicción.Ya 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 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 lapeek
acción puede invocarse en un orden arbitrario y al mismo tiempo.Entonces, lo más útil que puedes 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: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
peek
proporciona 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