Подтвердить что ты не робот

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

У меня проблема с потоком 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 working!
}
4b9b3361

Ответ 1

Использование 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 становится более очевидной.

Ответ 2

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

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;, поэтому просто используйте ! (логический не).

Ответ 3

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

Ответ 4

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

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

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

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

Самый простой подход, похоже, использует "return;" хотя.