Перерыв или возврат из Java 8 stream forEach?

при использовании внешние итерации на Iterable мы используем:break или return из расширенного цикла for-each как:

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

как мы можем break или return С помощью внутренние итерации в Java 8 лямбда-выражения как:

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

11 ответов


Если вам это нужно, вы не должны использовать forEach, но один из других методов, доступных в потоках; какой из них, зависит от вашей цели.

например, если целью этого цикла является поиск первого элемента, который соответствует некоторому предикату:

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

(Примечание: это не будет повторять всю коллекцию, потому что потоки лениво оцениваются - он остановится на первом объекте, который соответствует условию).

если вы просто хотите знать, если есть элемент в коллекции, для которого условие true, можно использовать anyMatch:

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

этой is возможно Iterable.forEach() (но не надежно с Stream.forEach()). Решение не очень приятное, но это is возможно.

предупреждение: вы не должны использовать его для управления бизнес-логикой, но исключительно для обработки исключительной ситуации, которая возникает во время выполнения forEach(). Например, ресурс внезапно перестает быть доступным, один из обрабатываемых объектов нарушает контракт (например, контракт говорит, что все элементы в потоке не должны быть null но вдруг и неожиданно один из них null) etc.

согласно документации для Iterable.forEach():

выполняет данное действие для каждого элемента Iterable до все элементы были обработаны или действие вызывает исключение... Исключения, действия передаются абоненту.

Итак, вы бросаете исключение, которое немедленно разорвет внутренний цикл.

код будет примерно такой - не могу сказать, что мне это нравится но это работает. Вы создаете свой собственный класс BreakException которая расширяет 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
}

заметил, что try...catch is не вокруг лямбда-выражения, а скорее вокруг всего 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
}

возврат в лямбде равен продолжению В for-each, но нет эквивалента перерыву. Вы можете просто сделать возврат, чтобы продолжить:

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

ниже вы найдете решение, которое я использовал в проекте. Вместо forEach просто использовать allMatch:

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

или вам нужно использовать метод, который использует предикат, указывающий, следует ли продолжать (поэтому у него есть перерыв)или вам нужно выбросить исключение - что, конечно, очень уродливый подход.

так что вы могли бы написать forEachConditional способ такой:

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

, а не Predicate<T>, вы можете определить свой собственный функциональный интерфейс с тем же общим методом (что-то принимая T и возврат bool), но с именами, которые указывают на ожидание более четко - Predicate<T> здесь не идеально.


вы можете использовать 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));

для максимальной производительности в параллельных операций использовать найти любое (), который похож на findFirst ().

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

однако, если требуется стабильный результат, вместо этого используйте findFirst ().

также обратите внимание, что совпадающие шаблоны (anyMatch()/allMatch) вернут только логическое значение, вы не получите сопоставленный объект.


Я достиг чего-то подобного

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

вы можете достичь этого, используя сочетание peek(..) и anyMatch(..).

используя Ваш пример:

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

или просто напишите общий метод util:

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

а затем используйте его, вот так:

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

это нелегко сделать с помощью API, предоставленных в JDK. Но мы можем сделать это с помощью AbacusUtil. Вот пример кода:

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

раскрытие информации: я разработчик AbacusUtil.


Как насчет этого:

окончательное условие BooleanWrapper = new BooleanWrapper ();

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

здесь BooleanWrapper - это класс, который необходимо реализовать для управления потоком.