Quebrar ou retornar do Java 8 stream forEach?

313

Ao usar a iteração externa sobre um Iterablenós usamos breakou returndo aprimorado para cada loop como:

for (SomeObject obj : someObjects) {
   if (some_condition_met) {
      break; // or return obj
   }
}

Como podemos breakou returnusando a iteração interna em uma expressão lambda do Java 8 como:

someObjects.forEach(obj -> {
   //what to do here?
})
Tapas Bose
fonte
6
Você não pode. Basta usar uma fordeclaração real .
Boann
1
possível duplicata de Java 8: Limite de fluxo infinito por um predicado
assylias
Claro que é possível forEach(). A solução não é boa, mas é possível. Veja minha resposta abaixo.
Honza Zidek
Considere outra abordagem: você apenas deseja não executar o código ; portanto, uma ifcondição simples dentro da forEachsolução fará o truque.
Thomas Decaux

Respostas:

374

Se você precisar disso, não deve usar forEach, mas um dos outros métodos disponíveis nos fluxos; qual deles depende de qual é seu objetivo.

Por exemplo, se o objetivo desse loop for encontrar o primeiro elemento que corresponda a algum predicado:

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findFirst();

(Nota: Isso não irá iterar a coleção inteira, porque os fluxos são avaliados preguiçosamente - ele pára no primeiro objeto que corresponde à condição).

Se você quiser apenas saber se há um elemento na coleção para o qual a condição é verdadeira, você pode usar anyMatch:

boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);
Jesper
fonte
7
Isso funciona se encontrar um objeto era o objetivo, mas esse objetivo geralmente é atendido por uma returndeclaração de um findSomethingmétodo. breaké mais comumente associado a um tipo de operação que demora um pouco .
Marko Topolnik
1
@MarkoTopolnik Sim, o pôster original não nos deu informações suficientes para saber qual é exatamente o objetivo; uma "demora enquanto" é uma terceira possibilidade, além das duas que mencionei. (Existe uma maneira simples de "levar um tempo" com os fluxos?).
Jesper
3
E quando o objetivo é implementar adequadamente o comportamento de cancelamento? A melhor coisa que podemos fazer é lançar uma exceção de tempo de execução dentro do forEach lambda quando notamos que o usuário solicitou um cancelamento?
User2163960
1
@HonzaZidek Editado, mas o ponto não é se é possível ou não, mas qual é o caminho certo para fazer as coisas. Você não deve tentar forçar o uso forEachpara isso; você deve usar outro método mais apropriado.
Jesper
1
@ Jesper Concordo com você, escrevi que não gostei da "solução de exceção". No entanto, sua formulação "Isso não é possível com forEach" estava tecnicamente incorreta. Também prefiro sua solução, no entanto, posso imaginar casos de uso em que a solução fornecida em minha resposta é preferível: quando o loop deve terminar por causa de uma exceção real . Concordo que você geralmente não deve usar as exceções para controlar o fluxo.
Honza Zidek
54

Isso é possível para Iterable.forEach()(mas não de maneira confiável Stream.forEach()). A solução não é boa, mas é possível.

AVISO : Você não deve usá-lo para controlar a lógica de negócios, mas apenas para lidar com uma situação excepcional que ocorre durante a execução do forEach(). Como um recurso repentinamente deixa de ser acessível, um dos objetos processados ​​está violando um contrato (por exemplo, o contrato diz que todos os elementos no fluxo não devem ser, de maneira nullrepentina e inesperada, um deles é null) etc.

De acordo com a documentação para Iterable.forEach():

Executa a ação especificada para cada elemento do Iterable até que todos os elementos tenham sido processados ​​ou a ação lança uma exceção ... As exceções lançadas pela ação são retransmitidas para o chamador.

Então você lança uma exceção que interrompe imediatamente o loop interno.

O código será algo assim - não posso dizer que gosto, mas funciona. Você cria sua própria classe BreakExceptionque se estende RuntimeException.

try {
    someObjects.forEach(obj -> {
        // some useful code here
        if(some_exceptional_condition_met) {
            throw new BreakException();
       }
    }
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}

Observe que o nãotry...catch está relacionado à expressão lambda, mas ao redor de todo o método. Para torná-lo mais visível, consulte a seguinte transcrição do código que mostra mais claramente:forEach()

Consumer<? super SomeObject> action = obj -> {
    // some useful code here
    if(some_exceptional_condition_met) {
        throw new BreakException();
    }
});

try {
    someObjects.forEach(action);
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}
Honza Zidek
fonte
37
Penso que esta é uma má prática e não deve ser considerada como uma solução para o problema. É perigoso, pois pode ser enganoso para um iniciante. De acordo com o Effective Java 2nd Edition, capítulo 9, item 57: 'Use exceções apenas para condições excepcionais'. Além disso 'Use exceções de tempo de execução para indicar erros de programação'. Em definitivo, encorajo vivamente qualquer pessoa que considere esta solução a procurar a solução @Jesper.
Louis F.
15
@LouisF. Eu disse explicitamente "Não posso dizer que gosto, mas funciona". O OP perguntou "como sair de forEach ()" e esta é uma resposta. Concordo plenamente que isso não deve ser usado para controlar a lógica de negócios. No entanto, posso imaginar alguns casos de uso úteis, como que uma conexão com um recurso repentinamente não esteja disponível no meio do forEach (), para o qual o uso de exceção não é uma prática recomendada. Eu adicionei um parágrafo à minha resposta para ficar claro.
Honza Zidek
1
Eu acho que essa é uma boa solução. Depois de pesquisar no Google por "exceções de java" e outras pesquisas com mais algumas palavras, como "práticas recomendadas" ou "desmarcadas" etc., vejo que há controvérsia sobre como usar exceções. Eu usei essa solução no meu código porque o fluxo estava executando um mapa que levaria minutos. Queria que o usuário pudesse cancelar a tarefa, verifiquei no início de cada cálculo o sinalizador "isUserCancelRequested" e lancei uma exceção quando verdadeira. É limpo, o código de exceção é isolado em uma pequena parte do código e funciona.
Jason
2
Note-se que Stream.forEachse não fornecer a mesma garantia forte sobre exceções sendo retransmitida para o chamador, então lançar uma exceção não é garantido para trabalhar desta forma para Stream.forEach.
Radiodef 4/08
1
@ Radiodef Esse é um ponto válido, obrigado. A postagem original era sobre Iterable.forEach(), mas eu adicionei seu ponto ao meu texto apenas pela integridade.
Honza Zidek
43

Um retorno em um lambda é igual a um continuar em um para cada, mas não há equivalente a uma interrupção. Você pode simplesmente fazer um retorno para continuar:

someObjects.forEach(obj -> {
   if (some_condition_met) {
      return;
   }
})
Aneesh Vijendran
fonte
2
Solução agradável e idiomática para atender aos requisitos gerais. A resposta aceita extrapola o requisito.
Davidxxx 01/03
6
em outras palavras, este não "quebra ou retorno de Java 8 fluxo forEach", que era a questão real
Jaroslav Záruba
1
No entanto, isso ainda "atrai" registros pelo fluxo de origem, o que é ruim se você estiver folheando algum tipo de conjunto de dados remoto.
Adrian Baker
23

Abaixo você encontra a solução que usei em um projeto. Em vez disso, forEachbasta usar allMatch:

someObjects.allMatch(obj -> {
    return !some_condition_met;
});
Julian Pieles
fonte
11

Ou você precisa usar um método que use um predicado indicando se deve continuar (para que ele ocorra) ou você deve lançar uma exceção - que é uma abordagem muito feia, é claro.

Então você pode escrever um forEachConditionalmétodo como este:

public static <T> void forEachConditional(Iterable<T> source,
                                          Predicate<T> action) {
    for (T item : source) {
        if (!action.test(item)) {
            break;
        }
    }
}

Em vez disso Predicate<T>, convém definir sua própria interface funcional com o mesmo método geral (algo usando Tae retornando a bool), mas com nomes que indiquem a expectativa mais claramente - Predicate<T>não é o ideal aqui.

Jon Skeet
fonte
1
Eu sugeriria que realmente usar a API do Streams e uma abordagem funcional aqui seja preferível à criação desse método auxiliar se o Java 8 estiver sendo usado de qualquer maneira.
skiwi
3
Essa é a takeWhileoperação clássica e essa pergunta é apenas uma daquelas que demonstram o quanto sua falta na API do Streams é sentida.
Marko Topolnik
2
@ Marko: takeWhile parece mais uma operação gerando itens, sem executar uma ação em cada um. Certamente, no LINQ no .NET, seria péssimo usar o TakeWhile com uma ação com efeitos colaterais.
Jon Skeet
9

Você pode usar o java8 + rxjava .

//import java.util.stream.IntStream;
//import rx.Observable;

    IntStream intStream  = IntStream.range(1,10000000);
    Observable.from(() -> intStream.iterator())
            .takeWhile(n -> n < 10)
            .forEach(n-> System.out.println(n));
frhack
fonte
8
O Java 9 oferecerá suporte para a operação takeWhile em fluxos.
pisaruk 20/09/16
6

Para obter o desempenho máximo em operações paralelas, use findAny (), que é semelhante ao findFirst ().

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findAny();

No entanto, se um resultado estável for desejado, use findFirst ().

Observe também que os padrões correspondentes (anyMatch () / allMatch) retornarão apenas booleanos; você não obterá o objeto correspondente.

Kanagavelu Sugumar
fonte
6

Atualize com Java 9+ com takeWhile:

MutableBoolean ongoing = MutableBoolean.of(true);
someobjects.stream()...takeWhile(t -> ongoing.value()).forEach(t -> {
    // doing something.
    if (...) { // want to break;
        ongoing.setFalse();
    }
});
user_3380739
fonte
3

Eu consegui por algo assim

  private void doSomething() {
            List<Action> actions = actionRepository.findAll();
            boolean actionHasFormFields = actions.stream().anyMatch(actionHasMyFieldsPredicate());
            if (actionHasFormFields){
                context.addError(someError);
            }
        }
    }

    private Predicate<Action> actionHasMyFieldsPredicate(){
        return action -> action.getMyField1() != null;
    }
Mohammad Adnan
fonte
3

Você pode conseguir isso usando uma mistura de peek (..) e anyMatch (..).

Usando seu exemplo:

someObjects.stream().peek(obj -> {
   <your code here>
}).anyMatch(obj -> !<some_condition_met>);

Ou apenas escreva um método util genérico:

public static <T> void streamWhile(Stream<T> stream, Predicate<? super T> predicate, Consumer<? super T> consumer) {
    stream.peek(consumer).anyMatch(predicate.negate());
}

E então use-o, assim:

streamWhile(someObjects.stream(), obj -> <some_condition_met>, obj -> {
   <your code here>
});
tuga
fonte
0

Que tal este:

final BooleanWrapper condition = new BooleanWrapper();
someObjects.forEach(obj -> {
   if (condition.ok()) {
     // YOUR CODE to control
     condition.stop();
   }
});

Onde BooleanWrapperestá uma classe que você deve implementar para controlar o fluxo.

Thomas Decaux
fonte
3
Ou AtomicBoolean?
koekje
1
Isso ignora o processamento do objeto quando !condition.ok(), no entanto, não impede o forEach()loop de todos os objetos de qualquer maneira. Se a parte lenta for a forEach()iteração e não seu consumidor (por exemplo, ela obtém objetos de uma conexão de rede lenta), essa abordagem não é muito útil.
zakmck 20/01
Sim, você está certo, minha resposta está completamente errada neste caso.
Thomas Decaux
0
int valueToMatch = 7;
Stream.of(1,2,3,4,5,6,7,8).anyMatch(val->{
   boolean isMatch = val == valueToMatch;
   if(isMatch) {
      /*Do whatever you want...*/
       System.out.println(val);
   }
   return isMatch;
});

Ele fará apenas a operação onde encontrar correspondência e, depois de encontrar correspondência, interrompa sua iteração.

Khairul Bashar Lemon
fonte