«1. Введение

Функциональные интерфейсы, предоставляемые JDK, не подготовлены должным образом для обработки проверенных исключений. Если вы хотите узнать больше о проблеме, прочтите эту статью.

В этой статье мы рассмотрим различные способы преодоления таких проблем с помощью функциональной Java-библиотеки Vavr.

Чтобы получить больше информации о Vavr и о том, как его настроить, ознакомьтесь с этой статьей.

2. Использование CheckedFunction

Vavr предоставляет функциональные интерфейсы с функциями, которые генерируют проверенные исключения. Это функции CheckedFunction0, CheckedFunction1 и так далее до CheckedFunction8. 0, 1, … 8 в конце имени функции указывают количество входных аргументов для функции.

Давайте рассмотрим пример:

static Integer readFromFile(Integer integer) throws IOException {
    // logic to read from file which throws IOException
}

Мы можем использовать описанный выше метод внутри лямбда-выражения без обработки IOException:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);

CheckedFunction1<Integer, Integer> readFunction = i -> readFromFile(i);
integers.stream()
 .map(readFunction.unchecked());

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

Мы должны проявлять осторожность при использовании этой функции с Stream API, так как исключение немедленно прервет операцию — остальную часть потока остановят.

3. Использование вспомогательных методов

Класс API предоставляет сокращенный метод для примера из предыдущего раздела:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);

integers.stream()
  .map(API.unchecked(i -> readFromFile(i)));

4. Использование подъема

Чтобы изящно обработать IOException, мы можем ввести стандартную попытку -catch блоки внутри лямбда-выражения. Однако краткость лямбда-выражения будет потеряна. Нам на помощь приходит подъем Вавра.

Подъем — это концепция из функционального программирования. Вы можете поднять частичную функцию до общей функции, которая возвращает Option в качестве результата.

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

Давайте перепишем пример из предыдущего раздела:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
 
integers.stream()
  .map(CheckedFunction1.lift(i -> readFromFile(i)))
  .map(k -> k.getOrElse(-1));

Обратите внимание, что результатом поднятой функции является Option, а результатом будет Option.None в случае исключения. Метод getOrElse() принимает альтернативное значение для возврата в случае Option.None.

5. Использование Try

Хотя метод lift() в предыдущем разделе решает проблему внезапного завершения программы, он фактически проглатывает исключение. Следовательно, потребитель нашего метода понятия не имеет, что привело к значению по умолчанию. Альтернативой является использование контейнера Try.

Try — это специальный контейнер, в который мы можем заключить операцию, которая может вызвать исключение. В этом случае результирующий объект Try представляет собой ошибку и оборачивает исключение.

Давайте посмотрим на код, использующий Try:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.stream()
  .map(CheckedFunction1.liftTry(i -> readFromFile(i)))
  .flatMap(Value::toJavaStream)
  .forEach(i -> processValidValue(i));

Чтобы узнать больше о контейнере Try и о том, как его использовать, ознакомьтесь с этой статьей.

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

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

Хотя эти функции позволяют нам изящно обрабатывать исключения, их следует использовать с особой осторожностью. С некоторыми из этих подходов потребители ваших методов могут быть удивлены неожиданными проверенными исключениями, хотя они явно не объявлены.