Обход элементов в Java 8 stream: аналог Continue в forEach

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Для ручного перехода между элементами потока можно использовать итератор. Он предлагает управляющие методы, вроде next(). В отличие от forEach, этот инструмент предоставляет больше контроля над итерациями.

Java
Скопировать код
Iterator<YourType> it = myStream.iterator();
while(it.hasNext()) {
    YourType item = it.next();
    // Обработка элемента
}

Итак, при с помощью итератора мы контролируем процесс итерации, обходя ограничения, которые ставит forEach. Важно помнить: потоки идеализированы для работы с каждым элементом в отдельности, но для детального контроля итераций следует прибегать к итератору.

Кинга Идем в IT: пошаговый план для смены профессии

Лямбды в контексте Java

В лямбда-выражениях forEach команда return не останавливает цикл, а лишь прекращает обработку текущего элемента. Каждый элемент обрабатывается лямбдами как отдельный метод.

Java
Скопировать код
list.stream().forEach(item -> {
    if (item.isSkipWorthy()) { // Запрос на пропуск элемента
        return; // Выход только из обработки текущего элемента
    }
    // Продолжение обработки
});

Здесь return выполняет функцию, аналогичную continue в обычных for и while циклах, но только в рамках лямбды.

Отсев элементов: плюсы, минусы и подводные камни

Метод filter предлагает возможность применения логики continue в инвертированном виде.

Java
Скопировать код
list.stream().filter(item -> !item.isSkipWorthy()).forEach(item -> {
    // Обработка только принятых элементов
});

С применением обратной логики (!condition) в условии вы сможете сосредоточиться на элементах, которые соответствуют вашему условию.

Улучшение читаемости кода

Превращение лямбд в явные методы усовершенствовывает читаемость и упрощает управление кодом.

Java
Скопировать код
list.stream().forEach(this::itemProcessor); // Метод-ссылка

private void itemProcessor(YourType item) {
    if (item.needsEarlyExit()) {
        return; // Только выход из текущей обработки
    }
    // Продолжение выполнения
}

В данном случае вполне очевидно, что return означает больше похож на небольшой перерыв, а не окончательный выход.

Всемогущий takeWhile

В Java 9 был добавлен метод takeWhile, который прекращает обработку элементов, как только встречает условие.

Java
Скопировать код
list.stream().takeWhile(item -> !item.stopOmeterReadingHigh()).forEach(this::itemProcessor);

С помощью takeWhile можно прекратить выполнение при условии, которое было задано.

Старшые методы все еще в строю!

Когда работа с потоками становится излишне сложной, вы всегда можете использовать обычные циклы с предварительной фильтрацией списка:

Java
Скопировать код
List<YourType> VIPsOnly = list.stream()
                               .filter(item -> !item.isSkipWorthy())
                               .collect(Collectors.toList());

for (YourType item : VIPsOnly) {
    // Здесь можно все: break, continue и даже изменять индекс цикла за чашкой кофе.
}

Классический цикл for-each возвращает объекту полный контроль над итерацией.

Визуализация

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

Java
Скопировать код
stream.forEach(item -> {
    System.out.println("Посетитель: " + item);
    // Билеты только в один конец, нет выходов и пропусков.
});

Маршрут:

Первая остановка:  🌳 (Наслаждаемся, без пропусков)
Следующая остановка: 🌴 (Хотелось бы пропустить, но у forEach нет такой функции)
Конечная остановка: 🎄 (Нет прямого пути к финишу)

Как бы мы не хотели сделать пересадку или выйти на определенной станции, потоки не предусматривают такой возможности 🚂💨

Объяснение при применении своего итератора

Для реализации сложных итераций вы можете применить свой итератор:

Java
Скопировать код
StreamEx.of(list).forEachIndexed((index, item) -> {
    if (itsASkipDay(item, index)) {
        return; // Действует как continue, готовы к следующему шагу
    }
    // Начало обработки элементов
});

Используя библиотеки вроде StreamEx и похожие инструменты, вы расширяете свои возможности и получаете более гибкое управление итерациями.

При использовании "поточной" обработки

Прежде чем воспользоваться потоками, рассмотрите следующие советы:

  1. Сложные сценарии с вложенными циклами: Здесь обычные циклы могут быть эффективнее.
  2. Множественные состояния или условия: Потоки могут лишь усложнить код.
  3. Тоска по break или continue: Вероятно, вам стоит вернуться к классическим методам.

Замечания по использованию потоков и их альтернатив

Будьте внимательны с потоками:

  • Параллельные потоки: Они не гарантируют порядок выполнения для forEach.
  • Состояние лямбд: Изменение состояния внутри лямбды может привести к неожиданному поведению.

Возможные альтернативы:

  • flatMap, map, reduce: Используйте эти методы или библиотеку StreamEx для реализации сложных сценариев итерации.

Полезные материалы

  1. Oracle JavaDocs: Stream Interface — подробно о Stream API от Oracle.
  2. Break or return from Java 8 stream forEach? — обсуждение возможностей прерывания или выхода из stream.forEach.
  3. Java Stream findFirst() with Example — шаг за шагом об использовании findFirst в потоках.