Java 8: Обязательная проверка обработки исключений в lambda-выражениях. Почему обязательный, а не факультативный?

Я играю с новыми функциями lambda в Java 8 и обнаружил, что практика, предлагаемая Java 8, действительно полезна. Тем не менее, мне интересно, есть ли хороший способ сделать обход для следующего сценария. Предположим, у вас есть shell пула объектов, которая требует своего рода фабрики для заполнения пула объектов, например (с помощью java.lang.functions.Factory ):

 public class JdbcConnectionPool extends ObjectPool { public ConnectionPool(int maxConnections, String url) { super(new Factory() { @Override public Connection make() { try { return DriverManager.getConnection(url); } catch ( SQLException ex ) { throw new RuntimeException(ex); } } }, maxConnections); } } 

После преобразования функционального интерфейса в lambda-выражение вышеописанный код выглядит следующим образом:

 public class JdbcConnectionPool extends ObjectPool { public ConnectionPool(int maxConnections, String url) { super(() -> { try { return DriverManager.getConnection(url); } catch ( SQLException ex ) { throw new RuntimeException(ex); } }, maxConnections); } } 

Не так уж и плохо, но проверенное исключение java.sql.SQLException требует блока try / catch внутри lambda. В моей компании мы используем два интерфейса в течение длительного времени:

  • IOut что эквивалентно java.lang.functions.Factory ;
  • и специальный интерфейс для случаев, которые обычно требуют проверки исключений: interface IUnsafeOut { T out() throws E; } interface IUnsafeOut { T out() throws E; } .

Предполагается, что IOut и IUnsafeOut будут удалены во время перехода на Java 8, однако нет точного соответствия для IUnsafeOut . Если lambda-выражения могут иметь дело с проверенными исключениями, например, они не были проверены, в конструкторе, описанном выше, можно было бы использовать просто следующее:

 super(() -> DriverManager.getConnection(url), maxConnections); 

Это выглядит намного чище. Я вижу, что я могу переписать ObjectPool чтобы принять наш IUnsafeOut , но, насколько я знаю, Java 8 еще не закончена, поэтому могут быть некоторые изменения, такие как:

  • что-то похожее на IUnsafeOut ? (честно говоря, я считаю, что грязный – субъект должен выбрать, что принять: либо Factory либо «небезопасная фабрика», которая не может иметь совместимые сигнатуры методов)
  • просто игнорируя проверенные исключения в lambdaх, поэтому нет необходимости в IUnsafeOut ? (почему бы и нет? Например, другое важное изменение: OpenJDK, который я использую, теперь javac не требует, чтобы переменные и параметры объявлялись как final должны быть зафиксированы в анонимном classе [функциональный интерфейс] или lambda-выражение)

Таким образом, обычно возникает вопрос: существует ли способ обойти проверенные исключения в lambdas или планируется ли это в будущем до тех пор, пока Java 8 не будет окончательно выпущен?


Обновление 1

Hm-mm, насколько я понимаю, что у нас сейчас есть, кажется, что на данный момент нет способа, несмотря на то, что упоминаемая статья датируется 2010 годом: Брайан Гетц объясняет прозрачность исключений в Java . Если в Java 8 ничего не изменилось, это можно было бы считать ответом. Также Брайан говорит, что interface ExceptionalCallable (то, что я упомянул как IUnsafeOut из нашего наследия кода), в значительной степени бесполезно, и я согласен с ним.

Я все еще скучаю по другому?

Не уверен, что я действительно отвечаю на ваш вопрос, но разве вы не могли бы просто использовать что-то подобное?

 public final class SupplierUtils { private SupplierUtils() { } public static  Supplier wrap(Callable callable) { return () -> { try { return callable.call(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } }; } } public class JdbcConnectionPool extends ObjectPool { public JdbcConnectionPool(int maxConnections, String url) { super(SupplierUtils.wrap(() -> DriverManager.getConnection(url)), maxConnections); } } 

В списке рассылки lambda это было подробно обсуждено . Как вы можете видеть, Брайан Гетц предложил там, что альтернатива – написать свой собственный комбинатор:

Или вы могли бы написать свой собственный тривиальный комбинатор:

 static Supplier exceptionWrappingSupplier(Supplier b) { return e -> { try { b.accept(e); } catch (Exception e) { throw new RuntimeException(e); } }; } 

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

Я бы предпочел, чтобы мы рассматривали это как «стек на 99% полный», а не альтернативу. Не все проблемы требуют новых языковых функций в качестве решений. (Не говоря уже о том, что новые языковые функции всегда вызывают новые проблемы.)

В те дни пользовательский интерфейс назывался Block.

Я думаю, это соответствует ответу JB Nizet .

Позже Брайан объясняет, почему это было спроектировано таким образом (причина проблемы)

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

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

Решения на базе библиотеки приводят к взрыву 2x в типах SAM (исключительные vs not), которые плохо взаимодействуют с существующими комбинаторными взрывами для примитивной специализации.

Доступные решения на основе языка были проигравшими от компромисса сложности / стоимости. Хотя есть некоторые альтернативные решения, которые мы собираемся продолжить исследовать – хотя явно не для 8 и, вероятно, не для 9.

В то же время у вас есть инструменты, чтобы делать то, что вы хотите. Я понимаю, что вы предпочитаете, чтобы мы предоставили вам последнюю милю (и, во-вторых, ваш запрос действительно является тонко завуалированным запросом «почему бы вам просто не отказаться от проверенных исключений уже»), но я думаю, что текущее состояние позволяет вы выполняете свою работу.

Сентябрь 2015 года:

Вы можете использовать ET для этого. ET – небольшая библиотека Java 8 для преобразования / перевода исключений.

С помощью ET вы можете написать:

 super(() -> et.withReturningTranslation(() -> DriverManager.getConnection(url)), maxConnections); 

Многострочная версия:

 super(() -> { return et.withReturningTranslation(() -> DriverManager.getConnection(url)); }, maxConnections); 

Все, что вам нужно сделать до этого, создает новый экземпляр ExceptionTranslator :

 ExceptionTranslator et = ET.newConfiguration().done(); 

Этот экземпляр является streamобезопасным, и его можно использовать несколькими компонентами. Вы можете настроить более конкретные правила преобразования исключений (например, FooCheckedException -> BarRuntimeException ), если хотите. Если других правил нет, проверенные исключения автоматически преобразуются в RuntimeException .

(Отказ от ответственности: я являюсь автором ET)

Считаете ли вы использование classа оболочки RuntimeException (непроверенный) для контрабанды исходного исключения из выражения lambda, а затем листинг завернутого исключения обратно в исходное исключенное исключение?

 class WrappedSqlException extends RuntimeException { static final long serialVersionUID = 20130808044800000L; public WrappedSqlException(SQLException cause) { super(cause); } public SQLException getSqlException() { return (SQLException) getCause(); } } public ConnectionPool(int maxConnections, String url) throws SQLException { try { super(() -> { try { return DriverManager.getConnection(url); } catch ( SQLException ex ) { throw new WrappedSqlException(ex); } }, maxConnections); } catch (WrappedSqlException wse) { throw wse.getSqlException(); } } 

Создание собственного уникального classа должно предотвращать вероятность ошибочного использования другого исключенного исключения для того, который вы завернули в свою лямбду, даже если исключение сериализуется где-то в конвейере, прежде чем вы поймаете и повторно выбросите его.

Хм … Единственное, что я вижу, это проблема в том, что вы делаете это внутри конструктора с вызовом super (), который по закону должен быть первым утверждением в вашем конструкторе. Считаете ли вы считаться предыдущим заявлением? У меня это работает (без конструктора) в моем собственном коде.

Мы разработали внутренний проект в моей компании, который помог нам в этом. Мы решили пойти публично два месяца назад.

Это то, что мы придумали:

 @FunctionalInterface public interface ThrowingFunction { R apply(T arg) throws E; /** * @param  type * @param  checked exception * @return a function that accepts one argument and returns it as a value. */ static  ThrowingFunction identity() { return t -> t; } /** * @return a Function that returns the result of the given function as an Optional instance. * In case of a failure, empty Optional is returned */ static  Function> lifted(ThrowingFunction f) { Objects.requireNonNull(f); return f.lift(); } static  Function unchecked(ThrowingFunction f) { Objects.requireNonNull(f); return f.uncheck(); } default  ThrowingFunction compose(final ThrowingFunction before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default  ThrowingFunction andThen(final ThrowingFunction after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } /** * @return a Function that returns the result as an Optional instance. In case of a failure, empty Optional is * returned */ default Function> lift() { return t -> { try { return Optional.of(apply(t)); } catch (Throwable e) { return Optional.empty(); } }; } /** * @return a new Function instance which wraps thrown checked exception instance into a RuntimeException */ default Function uncheck() { return t -> { try { return apply(t); } catch (final Throwable e) { throw new WrappedException(e); } }; } 

}

https://github.com/TouK/ThrowingFunction/

Обнаружение исключения описанным способом не работает. Я попробовал это, и я все еще получаю compiler errors, что на самом деле соответствует спецификации: выражение lambda генерирует исключение, которое несовместимо с целевым типом аргумента метода: Callable; call () не бросает его, поэтому я не могу передать выражение lambda как Callable.

Таким образом, в принципе нет решения: мы застреваем с написанием шаблона. Единственное, что мы можем сделать, это озвучить наше мнение о необходимости исправления. Я думаю, что спецификация не должна просто слепо отбрасывать целевой тип, основанный на несовместимых заброшенных исключениях: впоследствии он должен проверить, попало ли заброшенное несовместимое исключение или объявлено как броски в области вызова. И для lambda-выражений, которые не являются встроенными, я предлагаю отметить, что мы можем пометить их как тихое выбрасывание проверенного исключения (молчание в том смысле, что компилятор не должен проверять, но время выполнения все равно должно быть уловлено). давайте отметим их с помощью => в отличие от -> Я знаю, что это не дискуссионный сайт, но поскольку это единственное решение вопроса, позвольте себе быть услышанным и давайте сменить эту спецификацию!

Paguro предоставляет функциональные интерфейсы, которые переносят проверенные исключения . Я начал работать над этим через несколько месяцев после того, как вы задали свой вопрос, поэтому вы, вероятно, были частью вдохновения для этого!

Вы заметите, что в Paguro есть только 4 функциональных интерфейса и 43 интерфейса, включенных в Java 8. Это потому, что Paguro предпочитает generics для примитивов.

Пагуро имеет однопроходные преобразования, встроенные в его неизменные коллекции (скопированные из Clojure). Эти преобразования примерно эквивалентны Clojure-преобразователям или streamам Java 8, но они принимают функциональные интерфейсы, которые переносят проверенные исключения. Смотрите: различия между streamами Paguro и Java 8 .

Вы можете бросить свои лямбды, их просто нужно объявить «по-своему» (что делает их, к сожалению, непригодными для использования в стандартном коде JDK, но эй, мы делаем все, что можем).

 @FunctionalInterface public interface SupplierIOException { MyClass get() throws IOException; } 

Или более универсальная версия:

 public interface ThrowingSupplier { T get() throws E; } 

здесь . Существует также упоминание использования «sneakyThrow», чтобы не объявлять проверенные исключения, но затем все равно бросать их. Это немного больно моей голове, возможно, вариант.

  • Необязательный orElse Необязательный в Java
  • Связать Vs Лямбда?
  • Ошибка: в этом контексте поддерживаются только примитивные типы или типы перечислений EF
  • LINQ - Синтаксис запроса против цепей методов и lambda
  • Amazon Lambda для Firebase
  • Java 8 Streams: несколько фильтров или сложное условие
  • Идиома Java для lambda с интерфейсами, отличными от SAM
  • Как объединить список списков с одинаковыми типами элементов в один список элементов?
  • Перечислить сложную сортировку
  • Visual Studio отлаживает инструмент «быстрого просмотра» и lambda-выражения
  • Объединение двух выражений (выражение <Func >)
  • Interesting Posts

    Обновление до Windows 10 Pro, ошибок версии 1511, 10586 с 0x8024200D

    Класс расширяет React.Component не может использовать getInitialState в React

    Доступ к идентификатору / имени пользователя учетной записи Google через Android

    Обнаружение закрытия браузера или вкладки

    Post-Redirect-Получить с помощью ASP.NET

    Изменение десятичного разделителя в MySQL

    Переопределение метода java equals () – не работает?

    Почему Хаскелл (иногда) называют «Лучшим императивным языком»?

    Есть ли способ получить идентификатор электронной почты пользователя после проверки его / ее идентификатора Twitter с помощью OAuth?

    g ++ Как получить предупреждение об игнорировании возвращаемого значения функции

    Хостинг в Интернете с помощью xampp?

    Самый простой способ прочитать файл в String?

    Java – прокрутите до определенного текста внутри JTextArea

    Использование диска переустановки Dell для Windows Vista на ноутбуке Sony

    Как хранить очень большие цифры?

    Давайте будем гением компьютера.