Java 8:lambda 表达式中的强制检查异常处理。为什么是强制性的,而不是可选的?

2022-08-31 14:12:29

我正在使用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);
    }

}

将函数接口转换为 lambda 表达式后,上面的代码将变为:

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

}

确实不是那么糟糕,但是检查的异常需要在lambda内部有一个/块。在我的公司,我们长期使用两个接口:java.sql.SQLExceptiontrycatch

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

在迁移到 Java 8 的过程中,应该会同时删除 这两者,但是 没有完全匹配的 。如果 lambda 表达式可以像未选中一样处理选中的异常,则可以在上面的构造函数中简单地使用如下方式:IOut<T>IUnsafeOut<T>IUnsafeOut<T, E>

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

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

  • 实现类似于 ?(说实话,我认为这是肮脏的 - 主题必须选择接受什么:要么“不安全的工厂”,不能有兼容的方法签名)IUnsafeOut<T, E>Factory
  • 简单地忽略 lambda 中已检查的异常,因此不需要代理项?(为什么不呢?例如,另一个重要的变化:我使用的OpenJDK现在不需要将变量和参数声明为在匿名类[功能接口]或lambda表达式中捕获)IUnsafeOut<T, E>javacfinal

所以问题通常是:有没有办法绕过lambdas中经过检查的异常,还是在Java 8最终发布之前计划在将来?


更新 1

Hm-m-m,据我所知,我们目前似乎没有办法,尽管引用的文章可以追溯到2010年:Brian Goetz解释了Java中的异常透明度。如果Java 8中没有太大变化,这可以被认为是一个答案。Brian也说(我提到的超出我们的代码遗产)几乎是无用的,我同意他的观点。interface ExceptionalCallable<V, E extends Exception>IUnsafeOut<T, E extends Throwable>

我还会错过别的东西吗?


答案 1

不确定我是否真的回答了你的问题,但你不能简单地使用这样的东西吗?

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

答案 2

在 lambda 邮件列表中,对此进行了彻底的讨论。正如你所看到的,Brian Goetz建议另一种选择是编写自己的组合器:

或者你可以写你自己的平凡组合器:

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

您可以写一次,只需比编写原始电子邮件所花费的时间少。同样,对于您使用的每种SAM,一次。

我宁愿我们把它看作是“玻璃99%满”,而不是替代方案。并非所有问题都需要新的语言功能作为解决方案。(更不用说新的语言功能总是会导致新的问题。

在那些日子里,消费者接口被称为Block。

我认为这与JB Nizet的答案相吻合。

后来布莱恩解释了为什么这是这样设计的(问题的原因)

是的,您必须提供自己的特殊 SAM。但是,lambda转换将很好地与它们一起工作。

EG讨论了针对此问题的其他语言和库支持,并最终认为这是一个糟糕的成本/收益权衡。

基于库的解决方案导致 SAM 类型(特殊与非)的 2 倍爆炸,这些类型与原始特化的现有组合爆炸交互不良。

可用的基于语言的解决方案是复杂性/价值权衡的输家。虽然有一些替代解决方案,我们将继续探索 - 尽管显然不适合8,也可能不适合9。

与此同时,你有工具去做你想做的事。我知道你更喜欢我们为你提供最后一英里(其次,你的请求实际上是一个薄薄的请求,“你为什么不放弃检查的例外”),但我认为目前的状态可以让你完成工作。


推荐