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

1656625571 pochemu vy dolzhny ignorirovat isklyucheniya v java i kak eto

автор Райнер Ганекамп

KdtW2UtWR90oD8Q54rzNyTfQN24IafD1XJVQ
Фото Кристофа Веси на Unsplash

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

Исключения с проверкой и без флажков

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

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

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

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

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

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

Dgx2RgjqtCmRsIuyoTzcBVKr7BwUO4EnT1KK
Исключения с проверкой и без флажков

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

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

Логично, они не знают, есть ли у нашей программы альтернативные пути. Потому они оставляют решение за нами. Их ответственность состоит в том, чтобы «обозначить» методы, которые могут вызвать исключения. Эти ярлыки позволяют нам реализовать контрдействия.

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

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

LJlE2Wwpgm0D17OoIMoEUTEgyaGbeKRCwuaA
https://imgflip.com/i/26h3xi

Утрачена база данных

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

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

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

Как описано выше, заворачиваем DbConnectionException в а RuntimeException.

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

Обертка RuntimeException

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

public interface RuntimeExceptionWrappable<T> {  T execute() throws Exception;} public class RuntimeExceptionWrapper {  public static <T> T wrap(RuntimeExceptionWrappable<T> 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<DbConnection> wrappable = new RuntimeExceptionWrappable<DbConnection>() {      public DbConnection execute() throws Exception {        return new DbProvider().getConnection(username, password);      }    };    return RuntimeExceptionWrapper.wrap(wrappable);  }}

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

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

В Java 8 наш пример выше смотрится так:

@FunctionalInterfacepublic interface RuntimeExceptionWrappable<T> {  T execute() throws Exception;} public class DbConnectionRetrieverJava8 {  public DbConnection getDbConnection(String username, String password) {    return RuntimeExceptionWrapper.wrap(() ->      new DbProvider().getConnection(username, password));  }}

Разница достаточно очевидна: код более лаконичен.

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

RuntimeExceptionWrappable это очень общий интерфейс. Это просто функция, возвращающая значение. Случаи использования этой функции или ее вариантов возникают повсеместно. Для нашего удобства библиотека классов Java имеет встроенный набор столь распространенных интерфейсов. Они есть в упаковке java.util.function и более известные как «функциональные интерфейсы». Наши RuntimeExceptionWrappable похож на java.util.function.Supplier<;T>.

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

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

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

public List<URL> getURLs() {  return Stream    .of(“ “    .map(this::createURL)    .collect(Collectors.toList());} private URL createURL(String url) throws MalformedURLException {  return new URL(url);}

Есть большая проблема, когда дело доходит до исключений. Интерфейсы, определенные в java.util.function не создавайте исключений. Поэтому наш метод createURL не имеет такой же подписи, как java.util.function.Functionчто является параметром способа map.

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

public List<URL> getURLs() {  return Stream    .of(“ “    .map(url -> {      try {        return this.createURL(url);      } catch (MalformedURLException e) {        throw new RuntimeException(e);      }    })    .collect(Collectors.toList());}

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

@FunctionalInterfacepublic interface RuntimeWrappableFunction<T, R> {  R apply(T t) throws Exception;} public class RuntimeWrappableFunctionMapper {  public static <T, R> Function<T, R> wrap(    RuntimeWrappableFunction<T, R> wrappable) {      return t -> {        try {          return wrappable.apply

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *