Retorno de lambda forEach () en java

97

Estoy tratando de cambiar algunos bucles for-each a forEach()métodos lambda para descubrir las posibilidades de las expresiones lambda. Parece posible lo siguiente:

ArrayList<Player> playersOfTeam = new ArrayList<Player>();      
for (Player player : players) {
    if (player.getTeam().equals(teamName)) {
        playersOfTeam.add(player);
    }
}

Con lambda forEach()

players.forEach(player->{if (player.getTeam().equals(teamName)) {playersOfTeam.add(player);}});

Pero el siguiente no funciona:

for (Player player : players) {
    if (player.getName().contains(name)) {
        return player;
    }
}

con lambda

players.forEach(player->{if (player.getName().contains(name)) {return player;}});

¿Hay algún error en la sintaxis de la última línea o es imposible regresar del forEach()método?

samutamm
fuente
Todavía no estoy muy familiarizado con los aspectos internos de las lambdas, pero cuando me hago la pregunta: "¿De qué regresarías?", Mi sospecha inicial sería que no es el método.
Gimby
1
@Gimby Sí, returndentro de una declaración lambda regresa de la propia lambda, no de lo que sea que se llame lambda. El uso de terminar una secuencia antes ("cortocircuito") findFirstcomo se muestra en la respuesta de Ian Roberts .
Stuart Marks

Respuestas:

121

El returnno está regresando de la expresión lambda en lugar de a partir del método que contiene. En lugar de forEachnecesitar filterla transmisión:

players.stream().filter(player -> player.getName().contains(name))
       .findFirst().orElse(null);

Aquí filterrestringe la secuencia a aquellos elementos que coinciden con el predicado y findFirstluego devuelve un Optionalcon la primera entrada coincidente.

Esto parece menos eficiente que el enfoque de bucle for, pero de hecho findFirst()puede provocar un cortocircuito: no genera todo el flujo filtrado y luego extrae un elemento de él, sino que filtra solo tantos elementos como sea necesario para encuentra el primero que coincida. También puede usar en findAny()lugar de findFirst()si no necesariamente le importa obtener el primer reproductor coincidente de la transmisión (ordenada), sino simplemente cualquier elemento coincidente. Esto permite una mejor eficiencia cuando hay paralelismo involucrado.

Ian Roberts
fuente
¡Gracias, eso es lo que estaba buscando! Parece que hay muchas novedades en Java8 para explorar :)
samutamm
10
Razonable, pero sugiero que no lo use orElse(null)en un Optional. El punto principal de Optionales proporcionar una forma de indicar la presencia o ausencia de un valor en lugar de sobrecargar nulo (lo que conduce a NPE). Si lo usa optional.orElse(null), recupera todos los problemas con nulos. Lo usaría solo si no puede modificar la persona que llama y realmente espera un nulo.
Stuart Marks
1
@StuartMarks, de hecho, modificar el tipo de retorno del método a Optional<Player>sería una forma más natural de encajar en el paradigma de flujos. Solo estaba tratando de mostrar cómo duplicar el comportamiento existente usando lambdas.
Ian Roberts
for (Part part: parts) if (! part.isEmpty ()) return false; Me pregunto qué es realmente más corto. Y más claro. La API de Java Stream realmente ha destruido el lenguaje Java y el entorno Java. Pesadilla para trabajar en cualquier proyecto java en 2020.
mmm
17

Le sugiero que primero intente comprender Java 8 en su totalidad, lo más importante en su caso serán los flujos, las lambdas y las referencias a métodos.

Usted debe no convertir el código existente para Java 8 código en una base de línea por línea, se debe extraer y convertir los rasgos.

Lo que identifiqué en su primer caso es lo siguiente:

  • Desea agregar elementos de una estructura de entrada a una lista de salida si coinciden con algún predicado.

Veamos cómo lo hacemos, podemos hacerlo con lo siguiente:

List<Player> playersOfTeam = players.stream()
    .filter(player -> player.getTeam().equals(teamName))
    .collect(Collectors.toList());

Lo que haces aquí es:

  1. Convierta su estructura de entrada en una secuencia (supongo que aquí es de tipo Collection<Player>, ahora tiene un archivo Stream<Player>.
  2. Filtre todos los elementos no deseados con un Predicate<Player>, mapeando cada jugador al booleano verdadero si desea que se mantenga.
  3. Recopile los elementos resultantes en una lista, a través de a Collector, aquí podemos usar uno de los recopiladores de bibliotecas estándar, que es Collectors.toList().

Esto también incorpora otros dos puntos:

  1. Codifique contra interfaces, entonces codifique contra List<E>over ArrayList<E>.
  2. Use la inferencia de diamante para el parámetro de tipo en new ArrayList<>(), después de todo, está usando Java 8.

Ahora en su segundo punto:

Una vez más, desea convertir algo de Java heredado a Java 8 sin mirar el panorama general. Esta parte ya ha sido respondida por @IanRoberts , aunque creo que debes hacer players.stream().filter(...)...lo que sugirió.

skiwi
fuente
5

Si desea devolver un valor booleano, puede usar algo como esto (mucho más rápido que el filtro):

players.stream().anyMatch(player -> player.getName().contains(name));
Sriram
fuente
1

También puede lanzar una excepción:

Nota:

En aras de la legibilidad, cada paso del flujo debe aparecer en una nueva línea.

players.stream()
       .filter(player -> player.getName().contains(name))
       .findFirst()
       .orElseThrow(MyCustomRuntimeException::new);

si su lógica está vagamente "impulsada por excepciones", por ejemplo, hay un lugar en su código que detecta todas las excepciones y decide qué hacer a continuación. Solo use el desarrollo impulsado por excepciones cuando pueda evitar ensuciar su base de código con múltiples try-catchy lanzar estas excepciones son para casos muy especiales que espera y se pueden manejar correctamente).

nabster
fuente