Перерва або повернення з потоку Java 8 forEach?


312

При використанні зовнішньої ітерації над Iterableвикористанням ми breakабо returnчерез розширених для кожного циклу , як:

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твердження.
Боан


Звичайно, це можливо forEach(). Рішення не добре, але це можливо. Дивіться мою відповідь нижче.
Honza Zidek

Розглянемо інший підхід, ви просто хочете не виконувати код , так що проста ifумова всередині forEachволі зробить трюк.
Томас Деко

Відповіді:


373

Якщо вам це потрібно, ви не повинні використовувати forEach, але один із інших методів, доступних у потоках; яка з них, залежить від того, яка ваша мета.

Наприклад, якщо мета цього циклу - знайти перший елемент, який відповідає деякому предикату:

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

(Примітка. Це не повторить всю колекцію, оскільки потоки ліниво оцінені - вона зупиниться на першому об'єкті, що відповідає умові).

Якщо ви просто хочете дізнатися, чи є в колекції елемент, для якого умова справжня, ви можете використовувати anyMatch:

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

7
Це працює, якщо пошук об'єкта був ціллю, але цій цілі зазвичай виступає returnзаява з findSomethingметоду. breakчастіше асоціюється з режимом прийому під час роботи.
Марко Топольник

1
@MarkoTopolnik Так, оригінальний плакат не дав нам достатньої інформації, щоб знати, яка саме мета; "взяти час" - це третя можливість, окрім двох, про які я згадав. (Чи існує простий спосіб зробити "потоки" потоками?).
Jesper

3
Що робити, коли мета належним чином реалізувати поведінку скасування? Це найкраще, що ми можемо зробити лише для того, щоб кинути виняток із виконання часу всередині лямбда forEach, коли ми помічаємо, що користувач просив скасувати?
користувач2163960

1
@HonzaZidek Відредаговано, але справа не в тому, чи це можливо чи ні, а в тому, що правильно робити речі. Не слід намагатися змусити використовувати forEachдля цього; ви повинні використовувати інший, більш підходящий метод.
Jesper

1
@Jesper Я згоден з вами, я написав, що мені не подобається "Рішення винятків". Однак ваше формулювання "Це неможливо forEach" було технічно неправильним. Я також віддаю перевагу вашому рішенню, проте можу уявити випадки використання випадків, коли рішення, яке надано у моїй відповіді, є кращим: коли цикл слід закінчити через реальний виняток. Я погоджуюся, що ви не повинні використовувати винятки для контролю потоку.
Honza Zidek

53

Це є можливим 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
Я вважаю, що це погана практика, і її не слід розглядати як рішення проблеми. Це небезпечно, оскільки може ввести в оману для початківця. Згідно з Ефективним Java 2-го видання, глава 57, пункт 57: "Використовуйте винятки лише для виняткових умов" Крім того, "Використовуйте винятки під час виконання програми, щоб вказати на помилки програмування". Нарешті, я настійно рекомендую всім, хто розглядає це рішення, заглянути у вирішенні @Jesper.
Луї Ф.

15
@LouisF. Я прямо сказав: "Не можу сказати, що мені це подобається, але це працює". ОП запитала "як відірватися від forEach ()", і це відповідь. Я повністю згоден, що це не слід використовувати для контролю за логікою бізнесу. Однак я можу уявити такі корисні випадки використання, як, наприклад, підключення до ресурсу раптово недоступне в середині forEach () або так, для якого використання виключень не є поганою практикою. Я додав абзац до своєї відповіді, щоб бути зрозумілим.
Honza Zidek

1
Я думаю, що це прекрасне рішення. Після пошуку в Google «винятків Java» та інших пошуків із ще кількома словами, такими як «найкращі практики» або «неперевірені» тощо, я бачу суперечки щодо використання винятків. Я використовував це рішення у своєму коді, оскільки потік робив карту, яка займе кілька хвилин. Я хотів, щоб користувач міг скасувати завдання, тому я перевірив на початку кожного розрахунку на прапор "isUserCancelRequested" і кинув виняток, коли true. Це чисто, код виключення виділений на невелику частину коду, і він працює.
Джейсон

2
Зауважте, що Stream.forEachце не дає однакових надійних гарантій щодо виключень, що передаються абоненту, тому викидання виключень не гарантовано працює таким чином Stream.forEach.
Радіодеф

1
@Radiodef Це правдивий момент, дякую. Оригінал публікації був про Iterable.forEach(), але я додав вашу точку до мого тексту лише для повноти.
Honza Zidek

43

Повернення в лямбда дорівнює продовженню в кожному, але немає еквіваленту перерви. Ви можете просто зробити повернення для продовження:

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

2
Приємне та ідіоматичне рішення для вирішення загальної вимоги. Прийнята відповідь екстраполює вимогу.
davidxxx

6
Іншими словами, це не "Перерва або повернення з потоку Java 8 дляEach", що було актуальним питанням
Ярослав Заруба

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>, ви, можливо, захочете визначити власний функціональний інтерфейс тим же загальним методом (щось, що приймає Tта повертає a bool), але з іменами, які чіткіше вказують на очікування - Predicate<T>тут не ідеально.


1
Я б припустив, що насправді використання Streams API та функціонального підходу тут є кращим, ніж створення цього допоміжного методу, якщо Java 8 так чи інакше використовується.
skiwi

3
Це класична takeWhileоперація, і це питання є одним із тих, хто демонструє, наскільки відчувається його недолік в API Streams.
Марко Топольник

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

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

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

Або просто написати загальний метод утиліти:

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

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()ітерація, а не її споживач (наприклад, вона отримує об'єкти через повільне мережеве з'єднання), то такий підхід не дуже корисний.
zakmck

Так, ти маєш рацію, моя відповідь у цьому випадку зовсім неправильна.
Томас Деко

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

Він буде виконувати операцію лише там, де він знаходить збіг, а після пошуку відповідності зупиняє його ітерацію.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.