Обработка исключений, возникающих в потоках Java

Научитесь обрабатывать проверенные исключения, выдаваемые методами, используемыми в потоковых операциях в Java 8, используя стандартные методы, такие как безопасное извлечение методов и Optional.

1. Введение в проблему

Программа Java может остановить нормальный поток выполнения в следующем случае:

  • Проверенное исключение
  • Непроверенное исключение
  • Ошибка

Компилятор Java заставляет нас перехватывать и обрабатывать проверенные исключения, когда мы используем метод, который их выдает. Эти проверенные исключения ожидаемы, например, FileNotFoundException может быть выброшено, когда мы пытаемся прочитать файл из файловой системы.

В обычной программе Java мы можем использовать операторы try-catch-finally для перехвата таких проверенных исключений и соответствующей обработки. Например, Files.readString() — это один из методов, который выдает IOException, который необходимо обработать.

try {Files.readString(path);}catch(IOException e) {//handle exception}

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

 Список<Путь> pathList = List.of(Path.of("..."), Path.of("..."), Path.of("..."));Список<Строка> fileContents = pathList.stream().map(Файлы::readString).toList();//Ошибка компилятора: необработанное исключение: java.io.IOException

2. Использование try-catch в лямбда-выражении

Наиболее простое и очевидное решение — обернуть функцию в блок try-catch как лямбда-выражение и использовать ее в потоковых операциях.

List<String> fileContents = pathList.stream().map(path -> {try {return Files.readString(path);} catch(IOException e) {return null;}}).filter(Objects::nonNull).toList();

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

3. Использование безопасного метода извлечения

Одним из возможных лучших решений является извлечение блока try-catch в отдельный метод, который обрабатывает исключение и возвращает значение по умолчанию при возникновении такого исключения.

public class FileUtils {public static String safeReadString(Path path) {try {return Files.readString(path);} catch(IOException e) {return null;}}}

Теперь мы можем использовать этот метод в потоковой операции без каких-либо изъянов в коде, и эффект будет тот же.

List<String> fileContents = pathList.stream().map(FileUtils::safeReadString).filter(Objects::nonNull).toList();

4. Не выбрасывание исключения

Этот подход полезен, если у нас есть контроль над исходным кодом API. Если нет, мы можем использовать технику безопасного извлечения, чтобы обернуть API, выдающий исключения, в отдельный вызов метода.

В этой технике, как лучшая практика, мы можем возвращать экземпляр Optional, когда сталкиваемся с исключением. В результате это предотвратит NullPointerException дальше вниз по стеку вызовов и сделает код надежным.

public class FileUtils {static Optional<String> readString(Path path) {try {return Optional.of(Files.readString(path));} catch(IOException e) {return Optional.empty();}}}List<Optional<String>> fileContentsList = pathList.stream().map(HandleCheckedExceptions::readString).toList();

5. Использование FailableStream из Apache Commons Lang

FailableStream — это сокращенная и упрощенная версия Stream с сигнатурами провальных методов. Мы можем использовать его следующим образом:

List<String> fileContents = Streams.stream(pathList.stream()).map(Files::readString).collect(Collectors.toList());

Не забудьте включить последнюю версию библиотеки commons-lang3 в classpath проекта.

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version></dependency>

6. Заключение

В этом руководстве по Java Stream мы научились обрабатывать проверенные исключения, выдаваемые методами, используемыми в промежуточных операциях при обработке потока. Мы научились использовать встроенный блок try-catch в лямбда-выражениях и безопасное извлечение методов. В качестве практики bext рассмотрите возможность использования Optional вместо выдачи значения null из извлеченной функции.

Исходный код на Github

Прокрутить вверх