Passe para o próximo item usando o loop foreach Java 8 no fluxo

126

Eu tenho um problema com o fluxo de Java 8 foreach tentando mover para o próximo item no loop. Não consigo definir o comando como continue;, apenas return;funciona, mas você vai sair do loop neste caso. Preciso passar para o próximo item do loop. Como eu posso fazer isso?

Exemplo (não 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
    });
}

Exemplo (trabalhando):

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.
fonte
Publique um exemplo completo, mas mínimo, para que seja mais fácil de responder.
Tunaki,
Preciso passar para o próximo item do loop.
Viktor V.
2
O ponto dele é, com o código que você mostrou, não ter continueque mover para o próximo item sem qualquer alteração funcional de qualquer maneira.
DoubleDouble de
Se o objetivo é evitar a execução de linhas após a chamada para continuar, e que você não está nos mostrando, basta colocar todas essas linhas em um elsebloco. Se não houver nada depois continue, elimine o bloco if e continue: eles são inúteis.
JB Nizet

Respostas:

278

Usar return;vai funcionar muito bem. Isso não impedirá que o loop completo seja concluído. Ele apenas interromperá a execução da iteração atual do forEachloop.

Experimente o seguinte pequeno programa:

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);
    });
}

Resultado:

a
c

Observe como o return;é executado para a biteração, mas cimprime bem na iteração seguinte.

Por que isso funciona?

A razão pela qual o comportamento parece não intuitivo à primeira vista é porque estamos acostumados com a returninstrução que interrompe a execução de todo o método. Portanto, neste caso, esperamos que a mainexecução do método como um todo seja interrompida.

No entanto, o que precisa ser entendido é que uma expressão lambda, como:

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

    System.out.println(str);
}

... realmente precisa ser considerado como seu "método" distinto, completamente separado do mainmétodo, apesar de estar convenientemente localizado dentro dele. Então, realmente, a returninstrução apenas interrompe a execução da expressão lambda.

A segunda coisa que precisa ser entendida é que:

stringList.stream().forEach()

... é realmente apenas um loop normal oculto que executa a expressão lambda para cada iteração.

Com esses 2 pontos em mente, o código acima pode ser reescrito da seguinte maneira equivalente (apenas para fins educacionais):

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);
}

Com esse equivalente de código "menos mágico", o escopo da returndeclaração se torna mais aparente.

Sstan
fonte
3
Já tentei desta forma e por alguma razão não funcionou, repeti este truque novamente e está funcionando. Obrigado, isso me ajuda. Parece que estou sobrecarregado =)
Viktor V.
2
Funciona como um encanto! Você poderia adicionar uma explicação de por que isso funciona, por favor?
sparkyspider
2
@Spider: adicionado.
sstan
5

Outra solução: passe por um filtro com suas condições invertidas: Exemplo:

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

pode ser substituído por

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

A abordagem mais direta parece estar usando "return;" Apesar.

Anarkopsykotik
fonte
2

O lambda para o qual você está passando forEach()é avaliado para cada elemento recebido do fluxo. A iteração em si não é visível de dentro do escopo do lambda, portanto, você não pode continuecomo se forEach()fosse uma macro de pré-processador C. Em vez disso, você pode ignorar condicionalmente o restante das instruções nele.

John Bollinger
fonte
0

Você pode usar uma ifdeclaração simples em vez de continuar. Então, em vez da maneira como você tem seu código, você pode tentar:

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
    });
}

O predicado na instrução if será apenas o oposto do predicado em sua if(pred) continue;instrução, então apenas use !(não lógico).

Hassan
fonte