Estoy leyendo acerca de Java, arroyos y descubrir cosas nuevas como voy. Una de las nuevas cosas que encontré fue la peek() función. Casi todo lo que he leído en peek dice que debe de ser utilizado para la depuración de sus Secuencias.

Lo que si tuve un Arroyo donde cada Cuenta tiene un nombre de usuario, la contraseña y el login() y loggedIn() método.

También tengo

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

y

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

¿Por qué este ser tan malo?

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

Ahora como lo que puedo decir que esto es exactamente lo que pretende hacer. Ella;

  • Toma una lista de cuentas de
  • Intenta iniciar sesión en cada cuenta
  • Filtra cualquier cuenta que no se registra en
  • Recoge la registra en cuentas en una nueva lista de

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

La versión original de este usa la .método filter() de la siguiente manera;

.filter(account -> {
        account.login();
        return account.loggedIn();
    })
  • En cualquier momento me encuentro la necesidad de un multi-línea de lambda, puedo mover las líneas a un método privado y pasar el método de referencia en lugar de la expresión lambda.
  • Sí, yo entiendo esto. Yo sólo estaba tratando de forma más clara de demostrar lo que estoy tratando de lograr. Gracias 🙂
  • ¿Cuál es la intención – que están tratando de registro de todas las cuentas y filtrarlos en función de si se ha iniciado sesión (que puede ser trivialmente verdadero)? O, ¿quieres iniciar sesión en, luego filtrarlos en función de si son o no han iniciado sesión? Yo estoy pidiendo en este orden, porque forEach puede ser la operación que se desea como contraposición a peek. Sólo porque es en la API no quiere decir que no se abre para el abuso (como Optional.of).
  • Filtro basado en si ha iniciado la sesión. Por ejemplo, si el nombre de usuario que está mal es no iniciar la sesión. Así que, a continuación, desea comprobar si está o no está conectado. Si no lo es, va a ser sacudido por el filtro.
  • También tenga en cuenta que el código podría ser sólo .peek(Account::login) y .filter(Account::loggedIn); no hay ninguna razón para escribir un Consumidor y el Predicado que sólo llama a otro método como el que.
  • También tenga en cuenta que la corriente de la API explícitamente desalienta a los efectos secundarios en los parámetros de comportamiento.
  • Bueno por lo que tendría las siguientes desalentar? Consumer<Cuenta> login = cuenta -> getWebsite(de la cuenta.getUrl()).inicio de sesión(de la cuenta.getUsername(), cuenta.getPassword())
  • Útil a los consumidores siempre tienen efectos secundarios, aquellos que no están desalentados por supuesto. Este es mencionada en la misma sección: «Un pequeño número de secuencia de operaciones, tales como forEach() y peek(), sólo pueden funcionar a través de efectos secundarios; estos deben ser usados con cuidado.». Mi comentario iba más recordar que la peek operación (el cual está diseñado para propósitos de depuración) no debe ser sustituido por hacer la misma cosa dentro de otra operación como map() o filter().
  • Bien gracias. Por lo que yo llamaría forEach(inicio de sesión) en las cuentas. Luego, cuando quiero hacer algo con el registro en cuentas de filtro mediante una loggedIn predicado? Empezando a hacer más sentido ahora. Gracias
  • Sí, exactamente como en @Makoto la respuesta 🙂

InformationsquelleAutor Adam.J | 2015-11-10

6 Comentarios

  1. 64

    El dato fundamental de esta:

    No utilizar la API de una manera involuntaria, incluso si logra su objetivo inmediato. Ese enfoque puede romper en el futuro, y también es claro para el futuro de los mantenedores.


    No hay ningún daño en la ruptura de este a varias operaciones, como son operaciones distintas. Hay es daño en el uso de la API en un claro y no intencionado, que puede tener ramificaciones si este comportamiento determinado es modificado en futuras versiones de Java.

    Utilizando forEach en esta operación sería dejar claro que el mantenedor de que hay un la intención de efecto secundario en cada elemento de accounts, y que está realizando alguna operación que puede mutar ella.

    Es también más convencional en el sentido de que peek es un intermediario de la operación, que no opera en toda la colección hasta la terminal de operación se ejecuta, pero forEach es de hecho una operación en la terminal. De esta manera, usted puede hacer fuertes argumentos en torno a la conducta y el flujo del código, en contraposición a la formulación de preguntas acerca de si peek que se comporten de la misma como forEach en este contexto.

    accounts.forEach(a -> a.login());
    List<Account> loggedInAccounts = accounts.stream()
                                             .filter(Account::loggedIn)
                                             .collect(Collectors.toList());
    • Si realizar el inicio de sesión en un paso de preprocesamiento, usted no necesita una corriente en todo. Puede realizar forEach derecho en la colección de origen: accounts.forEach(a -> a.login());
    • Excelente punto. He incorporado en la respuesta.
    • Gran respuesta! Se debatía entre esto y lo que @Holger dijo. Ambos transmitir la misma cosa, pero esto parece un poco más claro y tiene un buen ejemplo también. Voy a seguir este enfoque, aunque dudo de que habrá otro desarrollador con esto, yo mismo se puede olvidar que la intención de la función.
    • A la derecha, mi respuesta se centró más en la cuestión general contenida en el título, es decir, este método es realmente sólo para la depuración, la explicación de los aspectos de ese método. Esta respuesta es más frescos y en su caso de uso y cómo hacerlo en su lugar. Así que, podríamos decir, en conjunto proporcionan una imagen completa. En primer lugar, la razón por la que esta no es la intención de uso, la segunda conclusión, no se pega a un uso no intencionado y qué hacer en su lugar. Este último tendrá un uso más práctico para usted.
    • Sí, me lo pueden ver. Una pena que no puedo aceptar molesta respuestas ja. Uno de esto es seguro, ambas respuestas me han ayudado definitivamente a entender por qué no hacer y cómo para lograr la misma cosa, de una forma más práctica manor.
    • Esto funciona en la OP del caso, donde la corriente puede ser fácilmente recreado. Sin embargo, si la secuencia no estaba basada en una colección, este enfoque no funciona tan bien. Usted podría hacer un argumento de que si el flujo está disponible sólo una vez, un semánticamente mejor manera de escribir esto sería .map(a -> { account.login(); return account; }), pero que parece un poco más detallado que un simple .peek(Account::login).
    • Si no es de una colección o de una matriz, que está haciendo algo con I/O y usted está tratando de actuar en una manera que probablemente no era la intención. La principal conclusión de este (además de la negrita uno) sería la realización de la operación de transformación antes filtro. La necesidad de transformar la y filtro en el mismo paso, puede ser visto como un caso extremo o un código de olor, y me gustaría inclinarse más hacia este último.
    • Si no es de una colección o de una matriz, que está haciendo algo con I/O y usted está tratando de actuar en una manera que probablemente no era la intención. «Eso no es necesariamente cierto en absoluto. Puedo escribir un método que toma un Stream<...> y, a continuación, llame a con myCollection.stream() sin hacer nada que OI de la base. Si las secuencias de la API se supone que para ser útil, no hay ninguna razón real por la que los métodos no podría llevarse a corrientes como argumentos.
    • Por supuesto, era mucho más fácil si la login() el método devuelve un boolean valor que indica el éxito de estado…
    • Me preguntaba si esta era la forma correcta de hacerlo o no? Tengo un par de anular los métodos que más tarde necesidad de comprobar que están de éxito. No estaba seguro de si esto debe ser dos o métodos que devuelven un valor booleano. Gracias por este.
    • También, podría yo hacer inicio de sesión de un predicado? Sería de registro, a continuación, volver loggedIn?
    • Eso es lo que me estaba apuntando. Si login() devuelve un boolean, se puede utilizar como un predicado que es la solución más limpia. Todavía tiene un efecto secundario, pero eso está bien siempre y como es la no-interferencia, es decir, el login proceso` de uno de Account no tiene ninguna influencia en el inicio de sesión` proceso` de otro Account.
    • Gracias! Que parece como la mejor solución!
    • ¿Cómo es el aceptado la respuesta? Estamos iterando sobre la colección dos veces, mientras que técnicamente sólo una iteración es necesario. Actualmente estoy enfrentando un problema similar y estoy considerando el uso de un clásico Bucle forEach. ¿Qué ventaja que tiene el uso de dos corrientes tienen aquí?
    • Una corriente presenta efectos secundarios, y el otro no. Si usted mezcla riachuelos, los cuales tienen efectos secundarios en los arroyos que no, se puede venir más difícil de depurar y mantener. Esto es en aras de la claridad. Al mismo tiempo, me había duda de que hay mucho en términos de rendimiento de ganar por iteración más de una vez; usted todavía está haciendo las mismas operaciones que en él, con la segunda operación, simplemente filtrar la infructuosamente cuentas autenticadas.
    • Obviamente, esta solución no es buena, ya que está obligando a una forma funcional de la escritura donde no funcionales código hace mucho más sentido. La función de inicio de sesión debe devolver un registran en la Cuenta de objeto o, al menos, un valor booleano o el clásico para cada bucle es un limpiador manera de escribir. Si usted tiene que repetir dos veces la misma colección, a continuación, el código está roto. Por favor, no utilizar la secuencia de la API para el bien de su uso.

  2. 90

    Lo importante que tenemos que entender, es que los flujos son impulsados por la operación en la terminal. El funcionamiento del terminal determina si todos los elementos tienen que ser procesados o ninguna en absoluto. Así collect es una operación en la que los procesos de cada elemento, mientras que findAny puede detener el procesamiento de elementos una vez que se encontró con un elemento coincidente.

    Y count() no puede procesar cualquier elemento cuando se puede determinar el tamaño de la corriente sin el procesamiento de los elementos. Ya que esta es una optimización no hecho en Java 8, pero que será en Java 9, podría haber sorpresas cuando se cambia a Java 9 y código de confiar en count() el procesamiento de todos los elementos. Esto también está conectado a otros dependientes de la implementación detalles, por ejemplo, incluso en Java 9, la implementación de referencia no será capaz de predecir el tamaño de una infinita fuente de la corriente combinada con limit mientras que no hay ninguna limitación fundamental en la prevención de tal predicción.

    Desde peek permite «realizar la acción en cada elemento como elementos son consumidos desde el flujo resultante de«, no el mandato de procesamiento de elementos, pero va a realizar la acción depende de lo que el funcionamiento del terminal necesidades. Esto implica que usted tiene que utilizar con mucho cuidado si usted necesita un procesamiento en particular, por ejemplo, queremos aplicar una acción sobre todos los elementos. Funciona si el funcionamiento del terminal está garantizado para procesar todos los elementos, pero incluso entonces, usted debe estar seguro de que no la próxima programador cambia la operación en la terminal (o se le olvida que el aspecto sutil).

    Más, mientras que los flujos de garantizar el mantenimiento del encuentro para que ciertas combinación de operaciones, incluso de corrientes paralelas, estas garantías no se aplican a peek. Al recopilar en una lista, la lista resultante tendrá el derecho de ordenar a las ordenes de corrientes paralelas, pero la peek acción se invoca en un orden arbitrario y al mismo tiempo.

    Así que la cosa más útil que se puede hacer con peek es averiguar si una secuencia de elemento ha sido procesados, lo que es exactamente lo que la documentación de la API dice:

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

    • no habrá ningún problema, futuro o presente, en la OP del caso de uso? Hace su código siempre hacer lo que él quiere?
    • tan lejos como puedo ver, no hay ningún problema en de esta forma exacta. Pero, como he tratado de explicar, utilizando de esta manera implica que usted tiene que recordar este aspecto, incluso si usted regresa al código de uno o dos años más tarde, para incorporar la «función de solicitud 9876» en el código…
    • «la mirada de acción posible que se invoca en un orden arbitrario y al mismo tiempo». No esta instrucción de ir en contra de su gobierno por cómo peek obra, por ejemplo, «como elementos que se consumen»?
    • Martínez: dice «como elementos son consumidos desde el flujo resultante de«, que no es la terminal de la acción, sino que el tratamiento, a pesar de la terminal de acción podría consumir elementos de pedido, siempre y cuando el resultado final es coherente. Pero también creo que, la frase de la API de nota, «ver los elementos por los que fluyen más allá de un cierto punto en una línea de tuberías» hace un mejor trabajo en la descripción del mismo.
  3. 17

    Quizás una regla general debe ser que si usas vistazo fuera de la «depuración» del escenario, sólo debe hacerlo si estás seguro de lo que la terminación de los intermedios y condiciones de filtrado son. Por ejemplo:

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

    parece ser un caso válido donde quieras, en una operación para transformar todos los Foos a los Bares y les digo 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 al final no la iteración de una colección de dos veces.

  4. 4

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

    Ahora la pregunta podría ser: debemos mutar los objetos de flujo o cambio de estado global desde dentro de las funciones en estilo funcional de programación java?

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

    Para mí la hora de elegir entre forEach() y peek(), es la elección de los siguientes: ¿quiero pedazos de código que convierten a los objetos de flujo para ser conectados a un extensible, o no me los quiere para conectar directamente a la corriente?

    Creo peek() mejor pareja con java9 métodos. por ejemplo, takeWhile() pueda decidir cuándo detener la iteración basados en una ya mutado objeto, de modo de emparejamiento con forEach() no tendría el mismo efecto.

    P. S. no he referencia map() a ninguna parte, porque en el caso de que deseemos mutar objetos (o global), en vez de generar nuevos objetos, funciona exactamente igual que peek().

  5. 2

    Aunque estoy de acuerdo con la mayoría de las respuestas de arriba, tengo un caso en el que el uso de peek en realidad parece la forma más limpia para ir.

    Similares en su caso, supongamos que desea filtrar sólo en cuentas activas y, a continuación, realizar un inicio de sesión en estas cuentas.

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

    Peek es útil para evitar la redundancia de la llamada, mientras que no tener que recorrer en iteración la colección dos veces:

    accounts.stream()
        .filter(Account::isActive)
        .map(account -> {
            account.login();
            return account;
        })
        .collect(Collectors.toList());
    • Todo lo que tienes que hacer es conseguir que el método de inicio de sesión a la derecha. Yo realmente no veo cómo el peek es la forma más limpia para ir. ¿Cómo debería de alguien que está leyendo su código de saber que usted realmente mal uso de la API. Buena y limpia el código no obliga a un lector a hacer suposiciones sobre el código.
  6. 0

    La solución funcional es para hacer de cuenta objeto inmutable. Así que cuenta.login() debe devolver una nueva cuenta de objeto. Esto significa que el mapa se puede utilizar para iniciar sesión en lugar de peek.

Dejar respuesta

Please enter your comment!
Please enter your name here