Java 8:在lambdaexpression式中强制检查exception处理。 为什么是强制性的,不可选

我正在使用Java 8中的新lambda特性,并发现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); } } 

在将函数接口转换为lambdaexpression式之后,上面的代码变得如下所示:

 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); } } 

实际上并不是那么糟糕,但检查的exceptionjava.sql.SQLException需要在lambda中有一个try / catch块。 在我的公司,我们长时间使用两个接口:

  • IOut<T> ,这相当于java.lang.functions.Factory ;
  • 以及通常需要检查exception传播的情况的特殊接口: interface IUnsafeOut<T, E extends Throwable> { T out() throws E; } interface IUnsafeOut<T, E extends Throwable> { T out() throws E; }

在迁移到Java 8的过程中, IOut<T>IUnsafeOut<T>都应该被删除,但是IUnsafeOut<T, E>没有完全匹配。 如果lambdaexpression式可以处理被检查的exception,就像它们被取消选中一样,那么就可以像上面的构造函数中那样简单地使用它:

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

这看起来更清洁。 我看到我可以重写ObjectPool超类接受我们的IUnsafeOut<T> ,但据我所知,Java 8还没有完成,所以可能会有一些变化,如:

  • 实现类似于IUnsafeOut<T, E> ? (说实话,我认为肮脏 – 主题必须select接受什么: Factory或“不安全的工厂”,不能有兼容的方法签名)
  • 简单地忽略lambdas中的检查exception,所以在IUnsafeOut<T, E>代理中不需要? (为什么不呢?例如另一个重要的变化:我使用的OpenJDK,现在的javac不需要声明variables和参数作为final来捕获匿名类[函数接口]或lambdaexpression式)

所以问题通常是:有没有办法绕过lambdas中的检查exception,或者是在未来计划,直到Java 8最终被释放?


更新1

嗯,据我了解我们目前有什么,似乎目前没有办法,尽pipe引用的文章是从2010年开始的: Brian Goetz解释了Java中的exception透明性 。 如果在Java 8中没有什么变化,这可以被认为是一个答案。 另外Brian说, interface ExceptionalCallable<V, E extends Exception> (我提到的IUnsafeOut<T, E extends Throwable>不在我们的代码中)几乎没用,我同意他的IUnsafeOut<T, E extends Throwable>

我还想念别的东西吗?

不知道我真的回答你的问题,但不能简单地使用类似的东西?

 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邮件列表中,我们对此进行了全面的讨论 。 正如你可以看到,布赖恩·戈茨在那里build议,另一种方法是编写自己的组合器:

或者你可以写你自己的小组合:

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

你可以只写一次,而不用花时间编写原始电子邮件。 而且对于你使用的每种types的SAM也是一样的。

我宁愿我们把这看作是“玻璃99%满”,而不是替代品。 并不是所有的问题都需要新的语言特性来解 (更不用说新的语言特征总是会引起新的问题。)

那时消费者接口被称为Block。

我认为这与JB Nizet的答案一致 。

后来Brian 解释了为什么这样devise(问题的原因)

是的,你必须提供你自己的特殊的SAMs。 但是,然后lambda转换将正常工作。

EG为此讨论了额外的语言和图书馆支持,最终认为这是一个糟糕的成本/收益折衷。

基于图书馆的解决scheme导致了SAMtypes的2倍爆炸(exceptionvs不),与原始专业化的现有组合爆炸相互作用很差。

可用的基于语言的解决scheme是复杂性/价值权衡的失败者。 尽pipe还有其他解决scheme,但我们仍将继续探索 – 尽pipe显然不是8,也可能不是9。

同时,你有工具来做你想做的事情。 我认为你更喜欢我们为你提供最后一英里(其次,你的请求实际上是一个“你为什么不放弃已经检查过的exception”的简单请求),但是我认为当前状态可以让你完成了你的工作。

你有没有考虑过使用RuntimeException(unchecked)包装类来将原始exception从lambdaexpression式中走私出来,然后将包装的exception转换原来的检查exception?

 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(); } } 

创build你自己独特的类应该可以防止错误地将你封装在你的lambda中的另一个未经检查的exception错误,即使在你捕获和重新抛出之前,exception在pipe道中的某个地方被序列化了。

嗯…我看到的唯一的问题就是你在做一个调用super()的构造函数,根据法则,这个函数必须是构造函数中的第一个语句。 作为以前的陈述,是否会算作考虑? 我有这个工作(没有构造函数)在我自己的代码。

2015年9月:

你可以用ET做这个。 ET是一个用于exception转换/转换的小型Java 8库。

用ET你可以写:

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

多行版本:

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

您之前需要做的就是创build一个新的ExceptionTranslator实例:

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

这个实例是线程安全的,可以被多个组件共享。 你可以configuration更具体的exception转换规则(如FooCheckedException -> BarRuntimeException ),如果你喜欢。 如果没有其他规则可用,则检查的exception会自动转换为RuntimeException

(免责声明:我是ET的作者)

我们在公司内部开发了一个内部项目,帮助我们做到这一点。 我们决定两个月前上市。

这就是我们想出的:

 @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); } }; } 

}

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

以所描述的方式包装exception不起作用。 我尝试了它,我仍然得到编译器错误,这实际上是根据规范:lambdaexpression式抛出与方法参数的目标types不兼容的exception:Callable; call()不会抛出它,所以我不能将lambdaexpression式作为Callable来传递。

所以基本上没有办法解决:我们一直在写样板文件。 我们唯一能做的就是expression我们的意见,这需要修复。 我认为规范不应该盲目放弃基于不兼容的抛出exception的目标types:它应该随后检查抛出的不兼容exception是否在调用范围内被捕获或声明为抛出。 而对于没有内联的lambdaexpression式,我们build议我们可以将它们标记为默默地抛出检查exception(在编译器不应该检查的意义上是无声的,但运行时仍应该捕获)。 让我们用=来标记那些,而不是 – >我知道这不是一个讨论网站,但是因为这是唯一的解决scheme,让自己听到,让我们改变这个规范!

Paguro提供了包装检查exception的function接口 。 在你提出你的问题几个月后,我开始研究它,所以你可能是它的灵感的一部分!

您会注意到Paguro中只有4个函数接口,而Java 8包含43个接口。这是因为Paguro更喜欢generics对于基本types。

Paguro的单一转换内置于其不变的集合(从Clojure复制)。 这些转换大致相当于Clojure转换器或Java 8stream,但它们接受包装已检查exception的function接口。 请参阅: Paguro和Java 8stream之间的差异 。

可以抛出你的lambdaexpression式,只需要用你自己的方式声明(不幸的是,它们不能在标准的JDK代码中重用,但是,嘿,我们尽我们所能)。

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

或者更通用的版本:

 public interface ThrowingSupplier<T, E extends Exception> { T get() throws E; } 

在这里参考。 还有一个提到使用“sneakyThrow”不宣布检查的exception,但仍然抛出他们。 这有点伤了我的头,也许是一个select。