69

我正在使用 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>代理?(为什么不呢?例如另一个重要的变化:我使用的 OpenJDKjavac现在不需要将变量和参数声明为final在匿名类 [功能接口] 或 lambda 表达式中捕获)

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


更新 1

嗯,据我了解我们目前所拥有的,目前似乎没有办法,尽管引用的文章是 2010 年的:Brian Goetz 解释 Java 中的异常透明度。如果在 Java 8 中没有发生太大变化,这可以被视为一个答案。Brian 还说interface ExceptionalCallable<V, E extends Exception>(我提到IUnsafeOut<T, E extends Throwable>的代码遗留问题)几乎没有用,我同意他的观点。

我还想念别的东西吗?

4

9 回答 9

49

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

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);
    }
}
于 2012-12-26T20:00:40.057 回答
34

在 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。

与此同时,你拥有做你想做的事的工具。我知道您更喜欢我们为您提供最后一英里(其次,您的请求实际上是一个隐晦的请求,即“您为什么不已经放弃检查的异常”),但我认为当前状态让你完成你的工作。

于 2014-03-27T13:59:03.033 回答
5

2015 年 9 月:

您可以为此使用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();

这个实例是线程安全的,可以被多个组件共享。您可以根据需要配置更具体的异常转换规则(例如FooCheckedException -> BarRuntimeException)。如果没有其他规则可用,检查的异常会自动转换为RuntimeException.

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

于 2015-09-02T17:52:58.383 回答
5

我们在我的公司开发了一个内部项目,帮助我们解决了这个问题。我们决定在两个月前上市。

这是我们想出的:

@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/

于 2016-04-04T05:31:44.803 回答
4

您是否考虑过使用 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();
    }
}

创建您自己的唯一类应防止将另一个未经检查的异常误认为是您包装在 lambda 中的异常,即使该异常在您捕获并重新抛出它之前已在管道中的某处序列化。

嗯...我在这里看到的唯一问题是您在构造函数中执行此操作并调用 super() ,根据法律,它必须是构造函数中的第一条语句。是否try算作先前的陈述?我在自己的代码中有这个工作(没有构造函数)。

于 2013-08-08T20:16:04.877 回答
1

以所述方式包装异常不起作用。我试过了,还是编译器报错,其实是按照规范的:lambda表达式抛出了与方法参数的目标类型不兼容的异常:Callable; call() 不会抛出它,所以我不能将 lambda 表达式作为 Callable 传递。

所以基本上没有解决方案:我们坚持编写样板文件。我们唯一能做的就是表达我们认为这需要修复的意见。我认为规范不应该仅仅基于不兼容的抛出异常盲目地丢弃目标类型:它应该随后检查抛出的不兼容异常是否在调用范围内被捕获或声明为 throws。对于未内联的 lambda 表达式,我建议我们可以将它们标记为静默抛出已检查异常(静默意味着编译器不应检查,但运行时仍应捕获)。让我们用 => 标记那些,而不是 -> 我知道这不是一个讨论网站,但由于这是问题的唯一解决方案,让我们听到自己的声音,让我们改变这个规范!

于 2013-01-13T17:54:06.173 回答
1

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

您会注意到 Paguro 中只有 4 个功能接口,而 Java 8 包含 43 个接口。这是因为 Paguro 更喜欢泛型而不是原语。

Paguro 在其不可变集合中内置了单通道转换(从 Clojure 复制)。这些转换大致相当于 Clojure 转换器或 Java 8 流,但它们接受包装检查异常的功能接口。请参阅:Paguro 和 Java 8 流之间的区别

于 2015-10-29T19:34:32.957 回答
1

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

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

或者更通用的版本:

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

参考这里。还提到了使用“sneakyThrow”来不声明检查的异常,但仍然抛出它们。这让我有点头疼,也许是一个选择。

于 2017-10-19T20:00:42.207 回答
0

jOOλ是一个库,它支持将各种抛出检查异常的功能接口包装到等效的 JDK 功能接口中。例如:

// Wraps the checked exception in an unchecked one:
Supplier<Class<?>> supplier = Unchecked.supplier(() -> Class.forName("com.example.X"));

// Re-throws the checked exception without compile time checking:
Supplier<Class<?>> supplier = Sneaky.supplier(() -> Class.forName("com.example.X"));

我在这篇博文中做了更多的例子。免责声明:我制作了 jOOλ

于 2021-10-07T14:05:49.890 回答