Научитесь обрабатывать проверенные исключения, выдаваемые методами, используемыми в потоковых операциях в 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 из извлеченной функции.