Рубрики
Без рубрики

Игнорирование исключений в Java

В этой статье я покажу, как игнорировать проверенные исключения в Java. Я начну с описания обоснования этого и общей схемы решения этой проблемы. Затем я представлю некоторые библиотеки для этой цели. С тегами java, обработка исключений, sneakythrows, lombok.

В этой статье я покажу, как игнорировать проверенные исключения в Java. Я начну с описания обоснования этого и общей схемы решения этой проблемы. Затем я представлю некоторые библиотеки для этой цели.

Проверенные и Непроверенные исключения

В Java метод может заставить вызывающий его объект иметь дело с возникновением потенциальных исключений. Вызывающий может использовать предложение try/catch, где try содержит фактический код, а catch содержит код для выполнения при возникновении исключения.

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

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

Один из вариантов – свести к минимуму этот шаблонный код. Мы можем обернуть исключение в RuntimeException, которое является непроверенным исключением. Это приводит к тому, что, даже если приложение по-прежнему выходит из строя, нам не нужно предоставлять какой-либо код обработки.

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

Мы вызываем исключения, мы должны написать дополнительный код для “проверенных исключений”, а другие исключения типа RuntimeException “непроверенные исключения”.

Зачем вообще проверять исключения?

Мы можем найти множество проверенных исключений в сторонних библиотеках и даже в самой библиотеке классов Java. Причина вполне очевидна. Поставщик библиотеки не может предсказать, в каком контексте разработчик будет использовать свой код.

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

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

Если наша база данных не является кэшем, приложение не сможет продолжать работать. И это нормально, если приложение выходит из строя:

Потерянная база данных

Давайте применим наш теоретический пример к реальному коду:

public DbConnection getDbConnection(String username, String password) {
  try {
    return new DbProvider().getConnection(username, password);
  } catch (DbConnectionException dce) {
    throw new RuntimeException(dce);
  }
}

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

Как описано выше, мы преобразуем исключение Db ConnectionException в исключение RuntimeException.

Требуемый код относительно подробный и всегда один и тот же. Это создает много дублирования и снижает удобочитаемость.

Оболочка RuntimeException

Мы можем написать функцию, чтобы упростить это. Он должен обернуть исключение RuntimeException поверх некоторого кода и вернуть значение. Мы не можем просто передавать код на Java. Функция должна быть частью класса или интерфейса. Что-то вроде этого:

public interface RuntimeExceptionWrappable {
  T execute() throws Exception;
}

public class RuntimeExceptionWrapper {
  public static  T wrap(RuntimeExceptionWrappable runtimeExceptionWrappable) {
    try {
      return runtimeExceptionWrappable.execute();
    } catch (Exception exception) {
      throw new RuntimeException(exception);
    }
  }
}

public class DbConnectionRetrieverJava7 {
  public DbConnection getDbConnection(final String username, final String password) {
    RuntimeExceptionWrappable wrappable = new RuntimeExceptionWrappable() {
      public DbConnection execute() throws Exception {
        return new DbProvider().getConnection(username, password);
      }
    };
    return RuntimeExceptionWrapper.wrap(wrappable);
  }
}

Перенос RuntimeException был извлечен в свой собственный класс. С точки зрения разработки программного обеспечения это может быть более элегантным решением. Тем не менее, учитывая объем кода, мы вряд ли можем сказать, что ситуация улучшилась.

С лямбдами Java 8 все стало проще. Если у нас есть интерфейс только с одним методом, то мы просто пишем конкретный код этого метода. Компилятор делает все остальное за нас. Ненужный или “синтаксический сахарный код” для создания определенного или анонимного класса больше не требуется. Это основной вариант использования лямбд.

В Java 8 наш приведенный выше пример выглядит следующим образом:

@FunctionalInterface
public interface RuntimeExceptionWrappable {
  T execute() throws Exception;
}

public class DbConnectionRetrieverJava8 {
  public DbConnection getDbConnection(String username, String password) {
    return RuntimeExceptionWrapper.wrap(() ->
      new DbProvider().getConnection(username, password));
  }
}

Разница совершенно очевидна. Код более лаконичен.

Исключения в Streams & Co.

RuntimeException Wrappable – это очень общий интерфейс. Это просто функция, которая возвращает значение. Варианты использования этой функции или ее вариаций появляются повсюду. Для нашего удобства Библиотека классов Java имеет встроенный набор таких общих интерфейсов. Они находятся в пакете java.util.function |/и более известны как "Функциональные интерфейсы". Наш RuntimeException Wrappable аналогичен java.util.function. Поставщик .

Эти интерфейсы являются предпосылкой мощного потока, дополнительных и других функций, которые также являются частью Java 8. В частности, Stream поставляется с множеством различных методов обработки коллекций. Многие из этих методов имеют “Функциональный интерфейс” в качестве параметра.

Давайте быстро поменяем вариант использования. У нас есть список строк URL, которые мы хотим отобразить в список объектов типа java.net.URL.

Следующий код не компилируется :

public List getURLs() {
  return Stream
    .of("https://www.hahnekamp.com", "https://www.austria.info")
    .map(this::createURL)
    .collect(Collectors.toList());
}

private URL createURL(String url) throws MalformedURLException {
  return new URL(url);
}

Существует большая проблема, когда дело доходит до исключений. Интерфейсы, определенные в java.util.function , не генерируют исключений. Вот почему наш метод createUrl не имеет той же сигнатуры, что и java.util.function. Функция , которая является параметром метода map.

Что мы можем сделать, так это записать блок try/catch внутри лямбда-выражения:

public List getURLs() {
  return Stream
    .of("https://www.hahnekamp.com", "https://www.austria.info")
    .map(url -> {
      try {
        return this.createURL(url);
      } catch (MalformedURLException e) {
        throw new RuntimeException(e);
      }
    })
    .collect(Collectors.toList());
}

Это компилируется, но тоже выглядит не очень хорошо. Теперь мы можем сделать еще один шаг и написать функцию-оболочку с новым интерфейсом, похожим на RuntimeException Wrappable :

@FunctionalInterface
public interface RuntimeWrappableFunction {
 R apply(T t) throws Exception;
}

public class RuntimeWrappableFunctionMapper {
 public static  Function wrap(
   RuntimeWrappableFunction wrappable) {
   return t -> {
     try {
       return wrappable.apply(t);
     } catch(Exception exception) {
       throw new RuntimeException(exception);
     }
   };
  }
}

И примените его к нашему примеру потока:

public List getURLs() {
  return Stream
    .of("https://www.hahnekamp.com", "https://www.austria.info")
    .map(RuntimeWrappableFunctionMapper.wrap(this::createURL))
    .collect(Collectors.toList());
}

private URL createURL(String url) throws MalformedURLException {
  return new URL(url);
}

Отлично! Теперь у нас есть решение, где мы можем:

  • запуск кода без перехвата проверенных исключений,
  • используйте лямбды, вызывающие исключения, в потоке, необязательно и т.д. ##SneakyThrow в помощь Библиотека SneakyThrow позволяет пропустить копирование и вставку фрагментов кода сверху. Полное раскрытие информации: я автор.

SneakyThrow поставляется с двумя статическими методами. Один запускает код без перехвата проверенных исключений. Другой метод переносит лямбда-выражение, вызывающее исключение, в один из функциональных интерфейсов:

//SneakyThrow returning a result
public DbConnection getDbConnection(String username, String password) {
  return sneak(() -> new DbProvider().getConnection(username, password));
}

//SneakyThrow wrapping a function
public List getURLs() {
  return Stream
   .of("https://www.hahnekamp.com", "https://www.austria.info")
   .map(sneaked(this::createURL))
   .collect(Collectors.toList());
}

Альтернативные библиотеки

Функция Метания

//ThrowingFunction returning a result
public DbConnection getDbConnection(String username, String password) {
  return unchecked(() -> 
       new DbProvider().getConnection(username, password))
    .get();
}

//ThrowingFunction returning a function
public List getURLs() {
  return Stream
    .of("https://www.hahnekamp.com", "https://www.austria.info")
    .map(unchecked(this::createURL))
    .collect(Collectors.toList());
}

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

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

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

Я разработал SneakyThrow как самоуверенную оболочку функции Throwing.

Вавр

Vavr, он же JavaSlang, является еще одной альтернативой. В отличие от функции SneakyThrow и Throwing, она предоставляет полный набор полезных функций, которые расширяют функциональность Java.

Например, он поставляется с сопоставлением шаблонов, кортежами, собственным потоком и многим другим. Если вы еще не слышали о нем, на него определенно стоит посмотреть. Приготовьтесь потратить некоторое время на то, чтобы полностью понять его потенциал.

//Vavr returning a result
public DbConnection getDbConnection(String username, String password) {
  return Try.of(() -> 
    new DbProvider().getConnection(username, password))
    .get();
}

//Vavr returning a function
public List getURLs() {
  return Stream
    .of("https://www.hahnekamp.com", "https://www.austria.info")
    .map(url -> Try.of(() -> this.createURL(url)).get())
    .collect(Collectors.toList());
}

Проект Ломбок

Такой список библиотек не будет полным без упоминания Ломбока. Как и Vavr, он предлагает гораздо больше функциональности, чем просто перенос проверенных исключений. Это генератор кода для шаблонного кода, который создает полноценные Java-компоненты, объекты Builder, экземпляры logger и многое другое.

Ломбок достигает своих целей с помощью манипуляций с байт-кодом. Поэтому нам требуется дополнительный плагин в нашей IDE.

@SneakyThrows – это аннотация Ломбока для преобразования функции с проверяемым исключением в функцию, которая этого не делает. Этот подход не зависит от использования лямбд, поэтому вы можете использовать его для всех случаев. Это наименее подробная библиотека.

Пожалуйста, имейте в виду, что Lombok манипулирует байт-кодом, что может вызвать проблемы с другими инструментами, которые вы можете использовать.

//Lombok returning a result
@SneakyThrows
public DbConnection getDbConnection(String username, String password) {
  return new DbProvider().getConnection(username, password);
}

//Lombok returning a function
public List getURLs() {
  return Stream
    .of("https://www.hahnekamp.com", "https://www.austria.info")
    .map(this::createURL)
    .collect(Collectors.toList());
}

@SneakyThrows
private URL createURL(String url) {
  return new URL(url);
}

Дальнейшее Чтение

Код доступен на GitHub

Оригинал: “https://dev.to/rainerhahnekamp/ignoring-exceptions-in-java-2ehn”