¿Romper o regresar de la secuencia Java 8 para cada uno?


313

Cuando usamos iteración externa sobre una Iterable, usamos breako returndesde un ciclo for-each mejorado como:

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

¿Cómo podemos breako returnusar la iteración interna en una expresión lambda de Java 8 como:

someObjects.forEach(obj -> {
   //what to do here?
})

66
No puedes Solo usa una fordeclaración real .
Boann


Por supuesto que es posible forEach(). La solución no es agradable, pero es posible. Vea mi respuesta a continuación.
Honza Zidek

Considere otro enfoque, solo desea no ejecutar el código , por lo tanto, una ifcondición simple dentro del forEachhará el truco.
Thomas Decaux

Respuestas:


374

Si necesita esto, no debe usar forEach, sino uno de los otros métodos disponibles en las transmisiones; cuál depende de cuál sea su objetivo.

Por ejemplo, si el objetivo de este ciclo es encontrar el primer elemento que coincida con algún predicado:

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

(Nota: Esto no repetirá toda la colección, porque las secuencias se evalúan de forma perezosa; se detendrá en el primer objeto que coincida con la condición).

Si solo desea saber si hay un elemento en la colección para el cual la condición es verdadera, puede usar anyMatch:

boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);

77
Esto funciona si encontrar un objeto era el objetivo, pero ese objetivo generalmente se cumple mediante una returndeclaración de un findSomethingmétodo. breakse asocia más habitualmente con una toma mientras que - tipo de operación.
Marko Topolnik

1
@MarkoTopolnik Sí, el póster original no nos ha proporcionado suficiente información para saber cuál es exactamente el objetivo; un "tiempo de espera" es una tercera posibilidad además de las dos que mencioné. (¿Hay una manera simple de hacer "tomar un tiempo" con las transmisiones?).
Jesper

3
¿Qué pasa cuando el objetivo es implementar correctamente el comportamiento de cancelación? ¿Es lo mejor que podemos hacer solo para lanzar una excepción de tiempo de ejecución dentro de forEach lambda cuando notamos que el usuario solicitó una cancelación?
user2163960

1
@HonzaZidek Editado, pero el punto no es si es posible o no, sino cuál es la forma correcta de hacer las cosas. No deberías intentar forzar el uso forEachpara esto; deberías usar otro método más apropiado en su lugar.
Jesper

1
@Jesper Estoy de acuerdo contigo, escribí que no me gustaba la "Solución de excepción". Sin embargo, su frase "Esto no es posible con forEach" era técnicamente incorrecta. También prefiero su solución, sin embargo, puedo imaginar casos de uso en los que la solución provista en mi respuesta es preferible: cuando el ciclo debe finalizar debido a una excepción real . Estoy de acuerdo en que generalmente no debe usar las excepciones para controlar el flujo.
Honza Zidek

54

Esto es posible para Iterable.forEach()(pero no confiablemente con Stream.forEach()). La solución no es agradable, pero es posible.

ADVERTENCIA : No debe usarlo para controlar la lógica de negocios, sino únicamente para manejar una situación excepcional que ocurre durante la ejecución de forEach(). Tal como un recurso deja de ser accesible de repente, uno de los objetos procesados ​​está violando un contrato (por ejemplo, el contrato dice que todos los elementos en la secuencia no deben ser, nullpero de repente e inesperadamente uno de ellos es null), etc.

De acuerdo con la documentación para Iterable.forEach():

Realiza la acción dada para cada elemento del Iterable hasta que todos los elementos hayan sido procesados ​​o la acción arroje una excepción ... Las excepciones lanzadas por la acción se transmiten a la persona que llama.

Entonces lanzas una excepción que inmediatamente romperá el ciclo interno.

El código será algo así: no puedo decir que me guste, pero funciona. Creas tu propia clase BreakExceptionque se extiende 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 notry...catch está alrededor de la expresión lambda, sino alrededor del método completo . Para hacerlo más visible, vea la siguiente transcripción del código que lo muestra más 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
}

37
Creo que esta es una mala práctica y no debe considerarse como una solución al problema. Es peligroso ya que podría ser engañoso para un principiante. Según Effective Java 2nd Edition, Capítulo 9, Artículo 57: 'Use excepciones solo para condiciones excepcionales'. Además, 'Use excepciones de tiempo de ejecución para indicar errores de programación'. En definitiva, recomiendo a cualquiera que esté considerando esta solución que busque en la solución @Jesper.
Louis F.

15
@LouisF. Dije explícitamente "No puedo decir que me gusta pero funciona". El OP preguntó "cómo romper con forEach ()" y esta es una respuesta. Estoy totalmente de acuerdo en que esto no debe usarse para controlar la lógica empresarial. Sin embargo, puedo imaginar algunos casos de uso útiles, como una conexión a un recurso que de repente no está disponible en el medio de forEach () más o menos, para el que usar una excepción no es una mala práctica. He agregado un párrafo a mi respuesta para que quede claro.
Honza Zidek

1
Creo que esta es una buena solución. Después de buscar en Google "excepciones de Java" y otras búsquedas con algunas palabras más como "mejores prácticas" o "sin marcar", etc., veo que existe controversia sobre cómo usar las excepciones. Usé esta solución en mi código porque la transmisión estaba realizando un mapa que tomaría minutos. Quería que el usuario pudiera cancelar la tarea, así que verifiqué al comienzo de cada cálculo la marca "isUserCancelRequested" y arrojé una excepción cuando es verdadero. Está limpio, el código de excepción está aislado en una pequeña porción del código y funciona.
Jason

2
Tenga en cuenta que Stream.forEachno no proporcionan la misma garantía fuerte acerca de las excepciones que se transmite a la persona que llama, por lo que no se garantiza una excepción a trabajar de esta manera para Stream.forEach.
Radiodef

1
@Radiodef Ese es un punto válido, gracias. La publicación original era sobre Iterable.forEach(), pero agregué su punto a mi texto solo por lo completo.
Honza Zidek

43

Un retorno en una lambda es igual a continuar en un para cada uno, pero no hay equivalente a un descanso. Solo puede hacer un regreso para continuar:

someObjects.forEach(obj -> {
   if (some_condition_met) {
      return;
   }
})

2
Solución agradable e idiomática para abordar el requisito general. La respuesta aceptada extrapola el requisito.
davidxxx

66
en otras palabras, esto no "interrumpe o regresa del flujo de Java 8 para cada uno", que era la pregunta real
Jaroslav Záruba

1
Sin embargo, esto todavía "extraerá" registros a través de la secuencia de origen, lo cual es malo si está buscando algún tipo de conjunto de datos remoto.
Adrian Baker,

23

A continuación encontrará la solución que utilicé en un proyecto. En su lugar, forEachsolo use allMatch:

someObjects.allMatch(obj -> {
    return !some_condition_met;
});

11

O necesita usar un método que usa un predicado que indica si debe continuar (por lo que tiene el descanso) o necesita lanzar una excepción, que es un enfoque muy feo, por supuesto.

Entonces podrías escribir un forEachConditionalmétodo como este:

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

En lugar de Predicate<T>eso, es posible que desee definir su propia interfaz funcional con el mismo método general (algo que toma un Ty devuelve un bool) pero con nombres que indican la expectativa más claramente, Predicate<T>no es ideal aquí.


1
Sugeriría que usar la API de Streams y un enfoque funcional aquí es preferible a crear este método auxiliar si se usa Java 8 de todos modos.
skiwi

3
Esta es la takeWhileoperación clásica , y esta pregunta es una de las que demuestran cuánto se siente su falta en la API de Streams.
Marko Topolnik

2
@Marko: takeWhile se siente más como si fuera una operación que produce elementos, no realiza una acción en cada uno. Ciertamente, en LINQ en .NET sería una forma deficiente utilizar TakeWhile con una acción con efectos secundarios.
Jon Skeet

9

Puede usar 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));

8
Java 9 ofrecerá soporte para la operación takeWhile en streams.
pisaruk

6

Para obtener el máximo rendimiento en operaciones paralelas, use findAny (), que es similar a findFirst ().

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

Sin embargo, si se desea un resultado estable, use findFirst () en su lugar.

También tenga en cuenta que los patrones coincidentes (anyMatch () / allMatch) devolverán solo booleanos, no obtendrá objetos coincidentes.


6

Actualice con Java 9+ con takeWhile:

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

3

He logrado algo como esto

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

3

Puede lograrlo usando una mezcla de peek (..) y anyMatch (..).

Usando tu ejemplo:

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

O simplemente escriba un método de utilidad genérico:

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

Y luego úsalo, así:

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

0

¿Qué hay de este?

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

¿Dónde BooleanWrapperhay una clase que debe implementar para controlar el flujo?


3
O AtomicBoolean?
Koekje el

1
Esto omite el procesamiento del objeto cuando !condition.ok(), sin embargo, no evita que se repita forEach()sobre todos los objetos de todos modos. Si la parte lenta es la forEach()iteración y no su consumidor (por ejemplo, obtiene objetos de una conexión de red lenta), entonces este enfoque no es muy útil.
zakmck

Sí, tienes razón, mi respuesta es bastante incorrecta en este 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;
});

Solo realizará operaciones donde encuentre coincidencia, y después de encontrar coincidencia, detendrá su iteración.


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.