Перейти к следующему элементу с помощью цикла foreach в Java 8 в потоке

127

У меня проблема с потоком Java 8 foreach, пытающимся перейти к следующему элементу в цикле. Я не могу установить команду вроде continue;, только return;работает, но в этом случае вы выйдете из цикла. Мне нужно перейти к следующему элементу цикла. Как я могу это сделать?

Пример (не работает):

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
            filteredLines = lines.filter(...).foreach(line -> {
           ...
           if(...)
              continue; // this command doesn't working here
    });
}

Пример (рабочий):

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
    filteredLines = lines.filter(...).collect(Collectors.toList());
}

for(String filteredLine : filteredLines){
   ...
   if(...)
      continue; // it's working!
}
Виктор В.
источник
Пожалуйста, опубликуйте полный, но минимальный пример, чтобы на него было легче ответить.
Tunaki
Мне нужно перейти к следующему элементу цикла.
Виктор В.
2
Его точка зрения заключается в том, что с кодом, который вы показали, в continueлюбом случае не будет перехода к следующему элементу без каких-либо функциональных изменений.
DoubleDouble
Если цель состоит в том, чтобы избежать выполнения строк, следующих после вызова для продолжения, и которые вы не показываете нам, просто поместите все эти строки в elseблок. Если после этого ничего нет continue, отбросьте блок if и continue: они бесполезны.
JB Nizet

Ответы:

279

Использование return;будет работать нормально. Это не помешает завершению полного цикла. Он остановит выполнение только текущей итерации forEachцикла.

Попробуйте следующую небольшую программу:

public static void main(String[] args) {
    ArrayList<String> stringList = new ArrayList<>();
    stringList.add("a");
    stringList.add("b");
    stringList.add("c");

    stringList.stream().forEach(str -> {
        if (str.equals("b")) return; // only skips this iteration.

        System.out.println(str);
    });
}

Вывод:

а
с

Обратите внимание, как return;выполняется для bитерации, но cна следующей итерации печатается нормально.

Почему это работает?

Причина, по которой поведение сначала кажется неинтуитивным, заключается в том, что мы привыкли, что returnоператор прерывает выполнение всего метода. Таким образом, в этом случае мы ожидаем, что mainвыполнение метода в целом будет остановлено.

Однако следует понимать, что лямбда-выражение, например:

str -> {
    if (str.equals("b")) return;

    System.out.println(str);
}

... действительно нужно рассматривать как отдельный «метод», полностью отдельный от mainметода, несмотря на то, что он удобно расположен внутри него. На самом деле returnоператор останавливает выполнение только лямбда-выражения.

Второе, что необходимо понять, это то, что:

stringList.stream().forEach()

... на самом деле это просто обычный скрытый цикл, который выполняет лямбда-выражение для каждой итерации.

С учетом этих двух моментов приведенный выше код можно переписать следующим эквивалентным способом (только в образовательных целях):

public static void main(String[] args) {
    ArrayList<String> stringList = new ArrayList<>();
    stringList.add("a");
    stringList.add("b");
    stringList.add("c");

    for(String s : stringList) {
        lambdaExpressionEquivalent(s);
    }
}

private static void lambdaExpressionEquivalent(String str) {
    if (str.equals("b")) {
        return;
    }

    System.out.println(str);
}

С этим «менее магическим» эквивалентом кода область действия returnоператора становится более очевидной.

sstan
источник
3
Я уже пробовал этот способ, и почему-то он не работает, я повторил этот трюк снова, и он работает. Спасибо, мне это помогает. Кажется, я просто переутомился =)
Виктор В.
2
Работает как шарм! Не могли бы вы объяснить, почему это работает?
Sparkyspider
2
@Spider: добавлено.
sstan
5

Другое решение: пройдите фильтр с вашими перевернутыми условиями: Пример:

if(subscribtion.isOnce() && subscribtion.isCalled()){
                continue;
}

можно заменить на

.filter(s -> !(s.isOnce() && s.isCalled()))

Кажется, что самый простой подход - использовать return; хотя.

Anarkopsykotik
источник
2

Лямбда, которую вы передаете, forEach()оценивается для каждого элемента, полученного из потока. Сама итерация не видна из области лямбда-выражения, поэтому вы не можете continueее использовать, как если бы forEach()это был макрос препроцессора C. Вместо этого вы можете условно пропустить остальные инструкции в нем.

Джон Боллинджер
источник
0

Вы можете использовать простой ifоператор вместо continue. Поэтому вместо того, как у вас есть код, вы можете попробовать:

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
            filteredLines = lines.filter(...).foreach(line -> {
           ...
           if(!...) {
              // Code you want to run
           }
           // Once the code runs, it will continue anyway
    });
}

Предикат в операторе if будет просто противоположен предикату в вашем if(pred) continue;операторе, поэтому просто используйте !(логическое не).

Hassan
источник