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

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (105)

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

}

确实不是那么糟糕,但检查的异常java.sql.SQLException需要lambda内的一个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>。如果lambda表达式可以处理经过检查的异常(如未经检查的异常),则可以像上面的构造函数中那样简单地使用它:

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

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

  • 实施类似的东西IUnsafeOut<T, E>?(说实话,我认为这是肮脏的 - 主体必须选择接受的内容:或者Factory不符合方法签名的“不安全工厂”)
  • 简单地忽略lambdas中的检查异常,所以没有必要在IUnsafeOut<T, E>代理?(为什么不呢?例如另一个重要的变化:我使用的OpenJDK javac现在不需要声明变量和参数以便final在匿名类[函数接口]或lambda表达式中捕获)

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

嗯,据我了解我们目前有什么,似乎目前没有办法,尽管引用的文章是从2010年开始的:Brian Goetz解释了Java中的异常透明性。如果Java 8中没有太多变化,这可以被认为是一个答案。另外Brian说interface ExceptionalCallable<V, E extends Exception>(我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邮件列表中进行了全面的讨论。正如你可以看到Brian Goetz在那里建议的另一种方法是编写你自己的combinator:

或者你可以编写你自己的简单组合器: static<T> Supplier<T> exceptionWrappingSupplier(Supplier<T> b) { return e -> { try { b.accept(e); } catch (Exception e) { throw new RuntimeException(e); } }; } 你可以写一次,而不用花时间编写原始电子邮件。对于你使用的每一种SAM同样也是一次。 我宁愿我们把它看作“玻璃99%满”,而不是替代品。并非所有的问题都需要新的语言功能来解决。(更不用说新的语言特征总是会引发新的问题。)

在那些日子里,Consumer界面被称为Block。

我认为这符合JB Nizet的回答

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

是的,你必须提供你自己的特殊SAMs。但是,lambda转换可以很好地与他们一起工作。 EG为此讨论了额外的语言和图书馆支持,并最终认为这是一种糟糕的成本/收益折衷。 基于库的解决方案会导致SAM类型爆发两次(特殊与非特殊),这与现有的原始专业化组合爆炸非常不利。 可用的基于语言的解决方案是复杂性/价值权衡的失败者。虽然有一些替代解决方案我们将继续探索 - 尽管显然不是8,也可能不是9。 与此同时,你有工具去做你想做的事。我知道你更喜欢我们为你提供最后一英里(其次,你的请求实际上是一个对“你为什么不放弃已检查的异常”的简单请求),但我认为当前状态可以让你完成了你的工作。

扫码关注云+社区

领取腾讯云代金券