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. В моей компании мы используем два интерфейса в течение длительного времени:
- Linq-запрос или выражение Lambda?
- Lambdas в classическом примере enums
- Лямбда-выражение и метод перегружают сомнения
- Получение имени свойства из выражения lambda
- java.lang.ClassCastException с использованием 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
из нашего наследия кода), в значительной степени бесполезно, и я согласен с ним.
Я все еще скучаю по другому?
- Java 8 Lambda Expressions - о нескольких методах в вложенном classе
- Какой смысл выражения лямбды?
- Почему вы используете Expression <Func >, а не Func ?
- Получить имя метода, используя выражение
- конвертировать список объектов из одного типа в другой, используя выражение lambda
- LINQ: Точечная запись или выражение запроса
- Java Lambda Stream Distinct () на произвольном ключе?
- Самый простой и аккуратный c ++ 11 ScopeGuard
Не уверен, что я действительно отвечаю на ваш вопрос, но разве вы не могли бы просто использовать что-то подобное?
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 super V, ? extends T, E> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default ThrowingFunction andThen(final ThrowingFunction super R, ? extends V, E> 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); } }; }
}
Обнаружение исключения описанным способом не работает. Я попробовал это, и я все еще получаю 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», чтобы не объявлять проверенные исключения, но затем все равно бросать их. Это немного больно моей голове, возможно, вариант.