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

313

При использовании внешней итерации над циклом 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?
})
Тапас бозе
источник
6
Ты не можешь Просто используйте реальное forутверждение.
Boann
Конечно, это возможно forEach(). Решение не хорошо, но это возможно. Смотрите мой ответ ниже.
Хонза Зидек
Рассмотрим другой подход, вы просто хотите не выполнять код , поэтому простое ifусловие внутри forEachсделает свое дело.
Томас Деко

Ответы:

374

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

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

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

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

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

boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);
Jesper
источник
7
Это работает, если целью является поиск объекта, но этой цели обычно служит returnоператор из findSomethingметода. breakчаще всего ассоциируется с операцией типа take while.
Марко Топольник
1
@MarkoTopolnik Да, оригинальный постер не дал нам достаточной информации, чтобы точно знать, какова цель; «взять время» - это третья возможность, помимо двух, о которых я упоминал. (Есть ли простой способ сделать "взять время" с потоками?).
Jesper
3
Как насчет того, когда цель состоит в том, чтобы правильно реализовать поведение отмены? Является ли лучшее, что мы можем сделать, просто выбросить исключение времени выполнения внутри лямбды forEach, когда мы заметим, что пользователь запросил отмену?
user2163960
1
@HonzaZidek Отредактировано, но дело не в том, возможно ли это или нет, а в том, как правильно делать вещи. Вы не должны пытаться принудительно использовать forEachдля этого; Вместо этого вы должны использовать другой, более подходящий метод.
Джеспер
1
@Джаспер Я согласен с тобой, я написал, что мне не понравилось «Исключение». Однако ваша формулировка «Это невозможно с forEach» была технически неверной. Я также предпочитаю ваше решение, однако я могу представить себе варианты использования, когда решение, представленное в моем ответе, предпочтительнее: когда цикл должен быть завершен из-за реального исключения. Я согласен, что вы не должны обычно использовать исключения для управления потоком.
Хонза Зидек
54

Это является возможным Iterable.forEach()(но не надежно с Stream.forEach()). Решение не хорошо, но это возможно.

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

Согласно документации для 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это не вокруг лямбда - выражения, а вокруг весь 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
Я считаю, что это плохая практика, и ее не следует рассматривать как решение проблемы. Это опасно, так как это может ввести в заблуждение новичка. Согласно Effective Java 2nd Edition, глава 9, пункт 57: «Используйте исключения только для исключительных условий». Кроме того, «Используйте исключения времени выполнения для обозначения ошибок программирования». В конечном итоге, я настоятельно рекомендую всем, кто рассматривает это решение, изучить решение @Jesper.
Луи Ф.
15
@LouisF. Я прямо сказал: «Не могу сказать, что мне это нравится, но это работает». ОП спросил "как оторваться от forEach ()" и это ответ. Я полностью согласен с тем, что это не должно использоваться для контроля бизнес-логики. Однако я могу представить несколько полезных вариантов использования, например, подключение к ресурсу, внезапно недоступное в середине forEach () или около того, для которого использование исключений не является плохой практикой. Я добавил параграф к моему ответу, чтобы быть ясным.
Хонза Зидек
1
Я думаю, что это прекрасное решение. После поиска в Google «исключений Java» и других поисков с помощью нескольких слов, таких как «лучшие практики» или «непроверенный» и т. Д., Я вижу, что существуют противоречия относительно того, как использовать исключения. Я использовал это решение в своем коде, потому что поток выполнял карту, которая занимала бы минуты. Я хотел, чтобы пользователь мог отменить задачу, поэтому я проверял в начале каждого вычисления флаг «isUserCancelRequested» и выдавал исключение, когда оно истинно. Это чисто, код исключения изолирован для небольшой части кода, и это работает.
Джейсон
2
Обратите внимание, что Stream.forEachэто не дает такой же строгой гарантии о том, что исключения передаются вызывающей стороне, поэтому создание исключения не обязательно будет работать таким образом Stream.forEach.
Radiodef
1
@Radiodef Это верный момент, спасибо. Оригинальный пост был о Iterable.forEach(), но я добавил вашу точку зрения в мой текст только для полноты.
Хонза Зидек
43

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

someObjects.forEach(obj -> {
   if (some_condition_met) {
      return;
   }
})
Аниш Вийендран
источник
2
Хорошее и идиоматическое решение для удовлетворения общих требований. Принятый ответ экстраполирует требование.
davidxxx
6
Другими словами, это не «Отрыв или возврат из потока Java 8 forEach», который был актуальным вопросом
Ярослав Заруба,
1
Это все равно будет «тянуть» записи через исходный поток, что плохо, если вы просматриваете какой-то удаленный набор данных.
Адриан Бейкер
23

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

someObjects.allMatch(obj -> {
    return !some_condition_met;
});
Джулиан Пилес
источник
11

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

Таким образом, вы можете написать такой forEachConditionalметод:

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

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

Джон Скит
источник
1
Я бы предположил, что на самом деле использование Streams API и функциональный подход здесь предпочтительнее, чем создание этого вспомогательного метода, если Java 8 так или иначе используется.
Скиви
3
Это классическая takeWhileоперация, и этот вопрос является лишь одним из тех, которые демонстрируют, насколько ощущается его недостаток в Streams API.
Марко Топольник
2
@Marko: takeWhile больше похоже на то, что это будет операция, дающая элементы, а не выполняющая действия над каждым. Конечно, в LINQ в .NET было бы плохо использовать TakeWhile с действием с побочными эффектами.
Джон Скит
9

Вы можете использовать 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
источник
8
Java 9 предложит поддержку операции takeWhile на потоках.
Писарук
6

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

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

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

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

Канагавелу Сугамар
источник
6

Обновление с помощью Java 9+ takeWhile:

MutableBoolean ongoing = MutableBoolean.of(true);
someobjects.stream()...takeWhile(t -> ongoing.value()).forEach(t -> {
    // doing something.
    if (...) { // want to break;
        ongoing.setFalse();
    }
});
user_3380739
источник
3

Я достиг чего-то вроде этого

  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

Вы можете добиться этого, используя сочетание 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>
});
Tuga
источник
0

Что насчет этого:

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

Где BooleanWrapperкласс, который вы должны реализовать для управления потоком.

Томас Деко
источник
3
Или AtomicBoolean?
Koekje
1
Это пропускает обработку объекта !condition.ok(), но, тем не менее, не предотвращает forEach()зацикливание всех объектов. Если медленная часть является forEach()итерацией, а не ее потребителем (например, она получает объекты из медленного сетевого соединения), то этот подход не очень полезен.
Закмк
Да, вы правы, мой ответ в этом случае совершенно неверен.
Томас Деко
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;
});

Он будет выполнять только ту операцию, в которой он находит совпадение, а после поиска совпадения останавливает свою итерацию.

Хайрул Башар Лимон
источник