1. Обзор
В Java 8 лямбда-выражения начали облегчать функциональное программирование, предоставляя краткий способ выражения поведения. Однако функциональные интерфейсы , предоставляемые JDK, не очень хорошо справляются с исключениями, и код становится многословным и громоздким, когда дело доходит до их обработки.
В этой статье мы рассмотрим некоторые способы борьбы с исключениями при написании лямбда-выражений.
2. Обработка Непроверенных Исключений
Во-первых, давайте разберемся в проблеме на примере.
У нас есть List , и мы хотим разделить константу, скажем, 50 на каждый элемент этого списка, и распечатать результаты:
Listintegers = Arrays.asList(3, 9, 7, 6, 10, 20); integers.forEach(i -> System.out.println(50 / i));
Это выражение работает, но есть одна проблема. Если какой-либо из элементов в списке 0 , тогда мы получаем ArithmeticException:/by zero . Давайте исправим это с помощью традиционного блока try-catch , чтобы мы регистрировали любое такое исключение и продолжали выполнение для следующих элементов:
Listintegers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> { try { System.out.println(50 / i); } catch (ArithmeticException e) { System.err.println( "Arithmetic Exception occured : " + e.getMessage()); } });
Использование try-catch решает проблему, но краткость лямбда-выражения теряется, и это уже не маленькая функция, как должно быть.
Чтобы справиться с этой проблемой, мы можем написать лямбда-оболочку для лямбда-функции . Давайте посмотрим на код, чтобы увидеть, как он работает:
static ConsumerlambdaWrapper(Consumer consumer) { return i -> { try { consumer.accept(i); } catch (ArithmeticException e) { System.err.println( "Arithmetic Exception occured : " + e.getMessage()); } }; }
Listintegers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(lambdaWrapper(i -> System.out.println(50 / i)));
Сначала мы написали метод-оболочку, который будет отвечать за обработку исключения, а затем передали лямбда-выражение в качестве параметра этому методу.
Метод оболочки работает так, как ожидалось, но вы можете возразить, что он в основном удаляет блок try-catch из лямбда-выражения и перемещает его в другой метод, и это не уменьшает фактическое количество строк записываемого кода.
Это верно в данном случае, когда оболочка специфична для конкретного случая использования, но мы можем использовать универсальные средства для улучшения этого метода и использовать его для множества других сценариев:
staticConsumer consumerWrapper(Consumer consumer, Class clazz) { return i -> { try { consumer.accept(i); } catch (Exception ex) { try { E exCast = clazz.cast(ex); System.err.println( "Exception occured : " + exCast.getMessage()); } catch (ClassCastException ccEx) { throw ex; } } }; }
Listintegers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach( consumerWrapper( i -> System.out.println(50 / i), ArithmeticException.class));
Как мы видим, эта итерация нашего метода-оболочки принимает два аргумента: лямбда-выражение и тип Исключения , который должен быть пойман. Эта лямбда-оболочка способна обрабатывать все типы данных, а не только Целые числа , и улавливать любой конкретный тип исключения, а не суперкласс Исключение .
Кроме того, обратите внимание, что мы изменили имя метода с lambda Wrapper на consumer Wrapper . Это связано с тем, что этот метод обрабатывает только лямбда-выражения для Функционального интерфейса типа Потребитель . Мы можем написать аналогичные методы обертки для других функциональных интерфейсов, таких как Function , BiFunction , BiConsumer и так далее.
3. Обработка Проверенных Исключений
Давайте изменим пример из предыдущего раздела и вместо печати на консоль запишем в файл.
static void writeToFile(Integer integer) throws IOException { // logic to write to file which throws IOException }
Обратите внимание, что приведенный выше метод может вызвать исключение IOException.
Listintegers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> writeToFile(i));
При компиляции мы получаем ошибку:
java.lang.Error: Unresolved compilation problem: Unhandled exception type IOException
Поскольку IOException является проверяемым исключением, мы должны обрабатывать его явно . У нас есть два варианта.
Во-первых, мы можем просто выбросить исключение за пределы нашего метода и позаботиться о нем в другом месте.
В качестве альтернативы мы можем обработать его внутри метода, который использует лямбда-выражение.
Давайте рассмотрим оба варианта.
3.1. Выбрасывание проверенного исключения из лямбда-выражений
Давайте посмотрим, что произойдет, когда мы объявим IOException в методе main :
public static void main(String[] args) throws IOException { Listintegers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> writeToFile(i)); }
Тем не менее, мы получаем ту же ошибку необработанного IOException во время компиляции .
java.lang.Error: Unresolved compilation problem: Unhandled exception type IOException
Это связано с тем, что лямбда-выражения похожи на Анонимные внутренние классы .
В нашем случае метод writeToFile является реализацией Consumer функционального интерфейса .
Давайте взглянем на определение Consumer :
@FunctionalInterface public interface Consumer{ void accept(T t); }
Как мы видим, метод accept не объявляет никаких проверенных исключений. Вот почему writeToFile не может вызывать исключение IOException.
Самым простым способом было бы использовать блок try-catch , обернуть проверенное исключение в непроверенное исключение и перестроить его:
Listintegers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> { try { writeToFile(i); } catch (IOException e) { throw new RuntimeException(e); } });
Это позволяет скомпилировать и запустить код. Однако этот подход вводит ту же проблему, которую мы уже обсуждали в предыдущем разделе, – он многословен и громоздок.
Мы можем добиться большего.
Давайте создадим пользовательский функциональный интерфейс с одним accept методом, который вызывает исключение.
@FunctionalInterface public interface ThrowingConsumer{ void accept(T t) throws E; }
А теперь давайте реализуем метод-оболочку, который способен переосмыслить исключение:
staticConsumer throwingConsumerWrapper( ThrowingConsumer throwingConsumer) { return i -> { try { throwingConsumer.accept(i); } catch (Exception ex) { throw new RuntimeException(ex); } }; }
Наконец, мы можем упростить способ использования метода writeToFile :
Listintegers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(throwingConsumerWrapper(i -> writeToFile(i)));
Это все еще своего рода обходной путь, но конечный результат выглядит довольно чистым и определенно легче поддерживать .
Как Бросающий потребитель , так и растущая потребительская обертка являются общими и могут быть легко повторно использованы в разных местах нашего приложения.
3.2. Обработка проверенного исключения в лямбда-выражении
В этом заключительном разделе мы изменим оболочку для обработки проверенных исключений.
Поскольку ваш интерфейс Throwing Consumer использует дженерики, мы можем легко обработать любое конкретное исключение.
staticConsumer handlingConsumerWrapper( ThrowingConsumer throwingConsumer, Class exceptionClass) { return i -> { try { throwingConsumer.accept(i); } catch (Exception ex) { try { E exCast = exceptionClass.cast(ex); System.err.println( "Exception occured : " + exCast.getMessage()); } catch (ClassCastException ccEx) { throw new RuntimeException(ex); } } }; }
Давайте посмотрим, как использовать его на практике:
Listintegers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(handlingConsumerWrapper( i -> writeToFile(i), IOException.class));
Обратите внимание, что приведенный выше код обрабатывает только IOException, в то время как любой другой вид исключения переосмысливается как RuntimeException .
4. Заключение
В этой статье мы показали, как обрабатывать конкретное исключение в лямбда-выражении без потери краткости с помощью методов-оболочек. Мы также узнали, как написать альтернативные варианты для функциональных интерфейсов, присутствующих в JDK, чтобы либо бросить, либо обработать проверенное исключение.
Другим способом было бы исследовать хитрый хак.
Полный исходный код функционального интерфейса и методов-оболочек можно загрузить из здесь и тестовых классов из здесь, на Github .
Если вы ищете готовые рабочие решения, Функция Метания проект стоит проверить.