Romper o volver de Java 8 stream forEach?


Cuando se usa la iteración externa sobre un Iterable usamos break o return de enhanced for-each loop como:

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

¿Cómo podemos break o return usar la iteración interna en una expresión lambda de Java 8 como:

someObjects.forEach(obj -> {
   //what to do here?
})
Author: Lii, 2014-04-26

11 answers

Si necesitas esto, no debes usar forEach, sino uno de los otros métodos disponibles en streams; cuál, depende de cuál sea tu objetivo.

Por ejemplo, si el objetivo de este bucle 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 iterará toda la colección, porque los flujos se evalúan perezosamente - se detendrá en el primer objeto que coincida con la condición).

Si solo quieres saber si hay un elemento en la colección para los que la condición es verdadera, puedes usar anyMatch:

boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);
 254
Author: Jesper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-17 13:49:49

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

ADVERTENCIA: No debe usarlo para controlar la lógica de negocio, sino puramente para manejar una situación excepcional que ocurre durante la ejecución del forEach(). 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 el flujo no deben ser null pero 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 de la Iterable hasta que todos los elementos hayan sido procesados o la acción lanza 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 bucle interno.

El código será algo como esto - No puedo decir que me guste pero funciona. Usted crea su propia clase BreakException que 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 el try...catch es no alrededor de la expresión lambda, sino alrededor de todo el método forEach(). Para hacerlo más visible, vea la siguiente transcripción del código que lo muestra más claramente:

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
}
 40
Author: Honza Zidek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-06 08:18:47

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

someObjects.forEach(obj -> {
   if (some_condition_met) {
      return;
   }
})
 20
Author: avijendr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-25 22:22:33

A continuación encontrará la solución que utilicé en un proyecto. En su lugar {[1] } solo use allMatch:

someObjects.allMatch(obj -> {
    return !some_condition_met;
});
 19
Author: Julian Liebl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-23 16:22:58

Ya sea necesitas usar un método que use un predicado que indique si debes seguir adelante (por lo que tiene el break en su lugar) o necesitas lanzar una excepción, lo cual es un enfoque muy feo, por supuesto.

Así que podrías escribir un método forEachConditional 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>, es posible que desee definir su propia interfaz funcional con el mismo método general (algo que toma un T y devuelve un bool) pero con nombres que indican la expectativa más claramente - Predicate<T> no es ideal aquí.

 11
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-26 07:49:19

Puedes 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
Author: frhack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-02 18:00:03

Para obtener el máximo rendimiento en operaciones paralelas utilice 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á objeto coincidente.

 4
Author: Kanagavelu Sugumar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-02 13:18:32

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
Author: Mohammad Adnan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-24 09:50:21

Puedes lograr eso usando una mezcla de peek(..) y anyMatch(..).

Usando su ejemplo:

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

O simplemente escriba un 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());
}

Y luego usarlo, así:

streamWhile(someObjects.stream(), obj -> <some_condition_met>, obj -> {
   <your code here>
});
 2
Author: tuga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-08 15:11:19

No es fácil hacerlo con las API proporcionadas en JDK. Pero podemos hacerlo por AbacusUtil . Aquí está el código de ejemplo:

Stream.of("a", "b", "c", "d", "e").peek(N::println) //
        .forEach("", (r, e) -> r + e, (e, r) -> e.equals("c"));
// print: a b c

Divulgación: Soy el desarrollador de AbacusUtil.

 2
Author: user_3380739,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-09 18:32:15

¿Qué hay de este:

Final BooleanWrapper condition = new BooleanWrapper ();

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

Donde BooleanWrapper es una clase que debe implementar para controlar el flujo.

 1
Author: Thomas Decaux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-04 12:15:26