Java 8: обязательная проверка обработки исключений в лямбда-выражениях. Почему обязательно, а не факультативно?
я играю с новыми функциями лямбда в Java 8 и обнаружил, что практики, предлагаемые Java 8, действительно полезны. Тем не менее, мне интересно, есть ли хороший способ сделать обход для следующего сценария. Предположим, у вас есть оболочка пула объектов, которая требует какой-то фабрики для заполнения пула объектов, например (используя java.lang.functions.Factory
):
public class JdbcConnectionPool extends ObjectPool<Connection> {
public ConnectionPool(int maxConnections, String url) {
super(new Factory<Connection>() {
@Override
public Connection make() {
try {
return DriverManager.getConnection(url);
} catch ( SQLException ex ) {
throw new RuntimeException(ex);
}
}
}, maxConnections);
}
}
после преобразования функционального интерфейса в лямбда-выражение код выше становится похожим что:
public class JdbcConnectionPool extends ObjectPool<Connection> {
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
блок внутри лямбды. В моей компании мы долгое время используем два интерфейса:
-
IOut<T>
, что эквивалентноjava.lang.functions.Factory
; - и специальный интерфейс для случаев, которые обычно требуют распространения проверенных исключений:
interface IUnsafeOut<T, E extends Throwable> { T out() throws E; }
.
и IOut<T>
и IUnsafeOut<T>
предполагается удалить во время миграции на Java 8, однако нет точного соответствия для IUnsafeOut<T, E>
. Если лямбда-выражения могут иметь дело с проверенными исключениями, как они были непроверены, можно использовать просто как следующее В конструкторе выше:
super(() -> DriverManager.getConnection(url), maxConnections);
это выглядит намного чище. Я вижу, что могу переписать ObjectPool
супер класс, чтобы принять наш IUnsafeOut<T>
, но, насколько я знаю, Java 8 еще не закончена, поэтому могут быть некоторые изменения, такие как:
- реализация чего-то похожего на
IUnsafeOut<T, E>
? (честно говоря, я считаю, что грязный-субъект должен выбрать, что принять: либоFactory
или "небезопасная фабрика", которая не может иметь совместимых сигнатур метода) - просто игнорируя проверенные исключения в lambdas, поэтому нет необходимости в
IUnsafeOut<T, E>
суррогаты? (почему бы и нет? например, еще одно важное изменение: OpenJDK, который я использую,javac
теперь не требует, чтобы переменные и параметры объявлялись какfinal
быть захваченным в анонимном классе [функциональный интерфейс] или лямбда выражение)
таким образом, вопрос обычно заключается в следующем: есть ли способ обойти проверенные исключения в lambdas или это планируется в будущем, пока Java 8 не будет окончательно выпущен?
обновление 1
Хм-м-м, насколько я понимаю, что у нас в настоящее время есть, кажется, на данный момент нет никакого способа, несмотря на то, что ссылочная статья датируется 2010 годом: Брайан Гетц объясняет прозрачность исключений в Java. Если в Java ничего не изменилось 8, это можно считать ответом. Также Брайан говорит, что interface ExceptionalCallable<V, E extends Exception>
(что я упомянул как IUnsafeOut<T, E extends Throwable>
из нашего наследия кода) в значительной степени бесполезен, и я согласен с ним.
я все еще скучаю по чему-то еще?
8 ответов
Не уверен, что я действительно отвечу на ваш вопрос, но вы не могли бы просто использовать что-то подобное?
public final class SupplierUtils {
private SupplierUtils() {
}
public static <T> Supplier<T> wrap(Callable<T> callable) {
return () -> {
try {
return callable.call();
}
catch (RuntimeException e) {
throw e;
}
catch (Exception e) {
throw new RuntimeException(e);
}
};
}
}
public class JdbcConnectionPool extends ObjectPool<Connection> {
public JdbcConnectionPool(int maxConnections, String url) {
super(SupplierUtils.wrap(() -> DriverManager.getConnection(url)), maxConnections);
}
}
в списке рассылки lambda это было тщательно обсудили. Как вы можете видеть, Брайан Гетц предложил там, что альтернативой является написать свой собственный комбинатор:
или вы можете написать свой собственный тривиальный комбинатор:
static<T> Supplier<T> exceptionWrappingSupplier(Supplier<T> b) { return e -> { try { b.accept(e); } catch (Exception e) { throw new RuntimeException(e); } }; }
вы можете написать его один раз, за меньшее время, которое потребовалось, чтобы написать оригинальное письмо. И точно так же один раз для каждого вида SAM вы используете.
Я бы предпочел, чтобы мы смотрели на это как" стекло 99% полное", а чем альтернатива. Не все проблемы требуют новых языковых функций, как решения. (Не говоря уже о том, что новые языковые функции всегда вызывают новые трудности.)
в те дни интерфейс потребителя назывался Block.
Я думаю, что это соответствует ЯБ Nizet это.
Позже Брайан объясняет, почему Это было разработано таким образом (причина проблемы)
Да, вы должны предоставить ваши собственные исключительные Сэмы. Но тогда лямбда с ними вполне могло бы сработать обращение.
например, обсуждалась дополнительная поддержка языка и библиотеки для этого проблема, и в конце концов почувствовал, что это было плохой ценой / выгодой компромисс.
библиотечные решения вызывают взрыв 2x в типах SAM (исключительные vs not), которые плохо взаимодействуют с существующими комбинаторными взрывами для примитивной специализации.
доступные языке решения были лузеры из компромисс сложности / ценности. Хотя есть и альтернатива решения мы будем продолжать изучать, хотя явно не для 8 и, вероятно,не для 9.
тем временем, у вас есть инструменты, чтобы делать то, что вы хотите. Я понимаю. вы предпочитаете, чтобы мы предоставили вам последнюю милю (и, во-вторых, запрос-это действительно тонко завуалированный запрос "почему бы вам просто отказаться от проверенных исключений уже"), но я думаю, что текущий государство позволяет вам сделать свою работу.
сентября 2015 года:
можно использовать ET для этого. ET-небольшая библиотека Java 8 для преобразования/перевода исключений.
С ET вы можете написать:
super(() -> et.withReturningTranslation(() -> DriverManager.getConnection(url)), maxConnections);
Multi line версия:
super(() -> {
return et.withReturningTranslation(() -> DriverManager.getConnection(url));
}, maxConnections);
все, что вам нужно сделать раньше, это создать новый ExceptionTranslator
например:
ExceptionTranslator et = ET.newConfiguration().done();
этот экземпляр является потокобезопасным и может использоваться несколькими компонентами. Можно настроить более конкретное преобразование исключений правила (например,FooCheckedException -> BarRuntimeException
), если вам нравится. Если других правил нет, проверенные исключения автоматически преобразуются в RuntimeException
.
(отказ от ответственности: я автор ET)
вы рассматривали возможность использования класса-оболочки RuntimeException (unchecked) для контрабанды исходного исключения из лямбда-выражения, а затем создания обернутого исключения назад это оригинальное проверенное исключение?
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();
}
}
создание собственного уникального класса должно предотвратить любую вероятность ошибки другого непроверенного исключения для того, которое вы обернули внутри лямбды, даже если исключение сериализуется где-то в конвейере, прежде чем вы поймаете и повторите бросок.
Мда... Единственное, что я вижу, что проблема здесь в том, что вы делаете это внутри конструктора с вызовом super (), который по закону должен быть первым оператором в вашем конструкторе. Делает try
count как предыдущий оператор? У меня это работает (без конструктора) в моем собственном коде.
мы разработали внутренний проект в моей компании, который помог нам в этом. Мы решили обнародовать это два месяца назад.
вот что мы придумали:
@FunctionalInterface
public interface ThrowingFunction<T,R,E extends Throwable> {
R apply(T arg) throws E;
/**
* @param <T> type
* @param <E> checked exception
* @return a function that accepts one argument and returns it as a value.
*/
static <T, E extends Exception> ThrowingFunction<T, T, E> 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 <T, R, E extends Exception> Function<T, Optional<R>> lifted(ThrowingFunction<T, R, E> f) {
Objects.requireNonNull(f);
return f.lift();
}
static <T, R, E extends Exception> Function<T, R> unchecked(ThrowingFunction<T, R, E> f) {
Objects.requireNonNull(f);
return f.uncheck();
}
default <V> ThrowingFunction<V, R, E> compose(final ThrowingFunction<? super V, ? extends T, E> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> ThrowingFunction<T, V, E> 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<T, Optional<R>> 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<T, R> uncheck() {
return t -> {
try {
return apply(t);
} catch (final Throwable e) {
throw new WrappedException(e);
}
};
}
}
обертывание исключения описанным способом не работает. Я попробовал, и я все еще получаю ошибки компилятора, которые на самом деле соответствуют спецификации: лямбда-выражение выдает исключение, несовместимое с целевым типом аргумента метода: Callable; call() не бросает его, поэтому я не могу передать лямбда-выражение как вызываемое.
таким образом, в основном нет решения: мы застряли с написанием шаблона. Единственное, что мы можем сделать, это высказать свое мнение, что это нужно исправить. Я думаю, что спецификация не должна просто слепо отбрасывать целевой тип на основе несовместимых брошенных исключений: она должна впоследствии проверить, поймано ли брошенное несовместимое исключение или объявлено как Броски в области вызова. И для лямбда-выражений, которые не встроены, я предлагаю отметить их как молчаливое выбрасывание проверенного исключения (молчание в том смысле, что компилятор не должен проверять, но среда выполнения все равно должна поймать). давайте пометим те с => в отличие от -> Я знаю, что это не дискуссионный сайт, но поскольку это единственное решение вопроса, позвольте себе быть услышанным и давайте изменим эту спецификацию!
Paguro предоставляет функциональные интерфейсы, которые обертывают проверенные исключения. Я начал работать над ним через несколько месяцев после того, как вы задали свой вопрос, так что вы, вероятно, были частью вдохновения для него!
вы заметите, что в Paguro есть только 4 функциональных интерфейса против 43 интерфейсов, включенных в Java 8. Это потому, что Пагуро предпочитает дженерики примитивам.
Paguro имеет однопроходные преобразования, встроенные в его неизменяемые коллекции (скопировано из Clojure). Эти преобразования примерно эквивалентны преобразователям Clojure или потокам Java 8, но они принимают функциональные интерфейсы, которые обертывают проверенные исключения. См.: различия между потоками Paguro и Java 8.
вы can бросьте из своих лямбд, они просто должны быть объявлены "по-своему" (что делает их, к сожалению, не повторно используемыми в стандартном коде JDK, но эй, мы делаем то, что можем).
@FunctionalInterface
public interface SupplierIOException {
MyClass get() throws IOException;
}
или более универсальная версия:
public interface ThrowingSupplier<T, E extends Exception> {
T get() throws E;
}
ref здесь. Существует также упоминание об использовании "sneakyThrow", чтобы не объявлять проверенные исключения, но затем все равно бросать их. Это немного болит голова, может быть, вариант.