Pasar al siguiente elemento usando el bucle foreach de Java 8 en la secuencia

126

Tengo un problema con la secuencia de Java 8 para cada uno que intenta pasar al siguiente elemento del ciclo. No puedo configurar el comando como continue;, solo return;funciona, pero saldrá del ciclo en este caso. Necesito pasar al siguiente elemento del bucle. ¿Cómo puedo hacer eso?

Ejemplo (no funciona):

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
            filteredLines = lines.filter(...).foreach(line -> {
           ...
           if(...)
              continue; // this command doesn't working here
    });
}

Ejemplo (en funcionamiento):

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
    filteredLines = lines.filter(...).collect(Collectors.toList());
}

for(String filteredLine : filteredLines){
   ...
   if(...)
      continue; // it's working!
}
Viktor V.
fuente
Publique un ejemplo completo pero mínimo para que sea más fácil de responder.
Tunaki
Necesito pasar al siguiente elemento del bucle.
Viktor V.
2
Su punto es que, con el código que ha mostrado, no tener continueque seguir moviéndose al siguiente elemento sin ningún cambio funcional de todos modos.
DoubleDouble
Si el objetivo es evitar la ejecución de líneas que vienen después de la llamada para continuar y que no nos está mostrando, simplemente coloque todas estas líneas en un elsebloque. Si no hay nada después continue, suelte el bloque if y continúe: son inútiles.
JB Nizet

Respuestas:

278

El uso return;funcionará bien. No evitará que se complete el ciclo completo. Solo dejará de ejecutar la iteración actual del forEachbucle.

Prueba el siguiente programa pequeño:

public static void main(String[] args) {
    ArrayList<String> stringList = new ArrayList<>();
    stringList.add("a");
    stringList.add("b");
    stringList.add("c");

    stringList.stream().forEach(str -> {
        if (str.equals("b")) return; // only skips this iteration.

        System.out.println(str);
    });
}

Salida:

una
c

Observe cómo return;se ejecuta para la biteración, pero se cimprime bien en la siguiente iteración.

¿Por qué funciona esto?

La razón por la que el comportamiento parece poco intuitivo al principio es porque estamos acostumbrados a que la returndeclaración interrumpa la ejecución de todo el método. Entonces, en este caso, esperamos que la mainejecución del método en su conjunto se detenga.

Sin embargo, lo que debe entenderse es que una expresión lambda, como:

str -> {
    if (str.equals("b")) return;

    System.out.println(str);
}

... realmente necesita ser considerado como su propio "método" distinto, completamente separado del mainmétodo, a pesar de estar convenientemente ubicado dentro de él. Entonces, realmente, la returndeclaración solo detiene la ejecución de la expresión lambda.

La segunda cosa que debe entenderse es que:

stringList.stream().forEach()

... es realmente solo un bucle normal bajo las cubiertas que ejecuta la expresión lambda para cada iteración.

Con estos 2 puntos en mente, el código anterior se puede reescribir de la siguiente manera equivalente (solo con fines educativos):

public static void main(String[] args) {
    ArrayList<String> stringList = new ArrayList<>();
    stringList.add("a");
    stringList.add("b");
    stringList.add("c");

    for(String s : stringList) {
        lambdaExpressionEquivalent(s);
    }
}

private static void lambdaExpressionEquivalent(String str) {
    if (str.equals("b")) {
        return;
    }

    System.out.println(str);
}

Con este equivalente de código "menos mágico", el alcance de la returndeclaración se vuelve más evidente.

sstan
fuente
3
Ya lo he intentado de esta manera y por alguna razón no funciona, he repetido este truco nuevamente y está funcionando. Gracias, me ayuda. Parece que estoy con exceso de trabajo =)
Viktor V.
2
¡Funciona de maravilla! ¿Podría agregar una explicación de por qué esto funciona, por favor?
Sparkyspider
2
@ Araña: agregado.
sstan
5

Otra solución: pasar por un filtro con sus condiciones invertidas: Ejemplo:

if(subscribtion.isOnce() && subscribtion.isCalled()){
                continue;
}

puede ser reemplazado con

.filter(s -> !(s.isOnce() && s.isCalled()))

El enfoque más sencillo parece utilizar "retorno"; aunque.

Anarkopsykotik
fuente
2

La lambda a la que está pasando forEach()se evalúa para cada elemento recibido de la secuencia. La iteración en sí no es visible desde el alcance de la lambda, por lo que no puede continuehacerlo como si forEach()fuera una macro de preprocesador de C. En su lugar, puede omitir condicionalmente el resto de las declaraciones que contiene.

John Bollinger
fuente
0

Puede usar una ifdeclaración simple en lugar de continuar. Entonces, en lugar de la forma en que tiene su código, puede intentar:

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
            filteredLines = lines.filter(...).foreach(line -> {
           ...
           if(!...) {
              // Code you want to run
           }
           // Once the code runs, it will continue anyway
    });
}

El predicado en la declaración if será el opuesto del predicado en su if(pred) continue;declaración, así que use !(no lógico).

Hassan
fuente