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

Lyu*_*riv 69 java lambda checked-exceptions java-8

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

}
Run Code Online (Sandbox Code Playgroud)

将函数接口转换为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);
    }

}
Run Code Online (Sandbox Code Playgroud)

确实不是那么糟糕,但是经过检查的异常java.sql.SQLException需要在lambda中使用try/ catchblock.在我公司,我们长时间使用两个接口:

  • 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);
Run Code Online (Sandbox Code Playgroud)

看起来更干净.我看到我可以重写ObjectPool超类接受我们的IUnsafeOut<T>,但据我所知,Java 8尚未完成,所以可能会有一些变化,如:

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

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


更新1

嗯-mm,据我所知,我们目前所拥有的,目前似乎没有办法,尽管参考文章的日期是2010年:Brian Goetz解释了Java中的异常透明度.如果在Java 8中没有什么变化,这可以被认为是一个答案.Brian也说interface ExceptionalCallable<V, E extends Exception>(我在IUnsafeOut<T, E extends Throwable>代码遗产中提到的内容)几乎没用,我同意他的看法.

我还想念别的吗?

JB *_*zet 43

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Gili Checked异常是对程序员的滥用,反之亦然.它们促进了样板代码,偶然的复杂性和彻底的编程错误.它们打破了程序流,特别是因为`try-catch`是一个语句而不是表达式.包装异常是a)令人讨厌,b)仅仅是黑客并解决基本的错误设计. (16认同)
  • 检查异常滥用的又一个很好的例子.在这种情况下,可能需要在RuntimeException中包装异常,但是无法在外部再次打开它会使整个检查异常点失效.请花点时间阅读为什么存在检查异常以及何时应该使用它们:http://stackoverflow.com/a/19061110/14731 (8认同)
  • 99%的实际程序逻辑需要检查和未检查的异常,以打破当前的工作单元并在顶级异常障碍中统一处理它们.那些异常是常规程序逻辑的一部分的罕见情况并不是程序员可能会错过的,并且需要编译器告诉他. (7认同)
  • 谢谢!是的,您的方法似乎是当前Java 8功能方面的可能解决方案(这也是Matt通过Guava Throwables发表评论时提出的建议http://java8blog.com/post/37385501926/fixing-checked-exceptions- in-java-8).它似乎也是最优雅的解决方案,但是,我想你也同意,这是一个小小的锅炉.Brian Goetz在2010年尝试使用类似_variadic类型的参数来解决这个问题,并且可能Java会以某种方式解决这个问题,谁知道呢. (3认同)
  • @Gili您的例子正是我提到的*罕见案例*.在服务器端,这种情况甚至更少.至于你与C的比较,异常 - 与错误代码不同 - 只能用*显式*忽略,使用空的catch块.有趣的是,这正是开发人员被迫对检查异常做一些事情时发生的事情.你声明我*不喜欢*异常听起来像你试图搞笑,但我很抱歉错过了幽默. (2认同)
  • @gili我在双方都有丰富的经验,尽管存在细微差别,但所有例外与需要提前发现的例外的比例仍然非常大.例如,用户输入在作用之前被*验证*,并且包括诸如文件是否存在使用`file.exists()`的检查,这使得后续的`FileNotFoundException`出现奇怪且意外的事件. (2认同)
  • @Gili 现在你已经发明了自己的主张并将其分配给我。我在哪里可以声明应用程序在遇到异常时应该“崩溃”?我说*当前的工作单元将被中止*。现在你告诉我,当 WiFi 连接丢失时,当前的工作单元应该如何*继续*?如果您考虑到一些启发式方法,将不断重试直到重新建立连接等等,那么这确实是在工作单元内处理异常的*特殊情况*的示例。对于大多数目的来说,这种启发式方法是不值得的,简单地中止就足够了。 (2认同)
  • @MarkoTopolnik,我撤回了关于面对WIFI连接崩溃时软件崩溃的评论.我们可以就这个主题进行一场非常有趣的辩论,但Stackoverflow的评论系统在这种讨论中效果不佳(评论太短,太异步).我们现在同意不同意;) (2认同)

Edw*_*rzo 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); }
     };
}
Run Code Online (Sandbox Code Playgroud)

您可以编写一次,而不是写入原始电子邮件所花费的时间.同样适用于您使用的每种SAM.

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

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

我认为这符合JB Nizet的答案.

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

是的,您必须提供自己的特殊SAM.但是lambda转换对它们来说可以正常工作.

EG讨论了这个问题的附加语言和库支持,最后认为这是一个糟糕的成本/收益权衡.

基于库的解决方案导致SAM类型爆炸2倍(异常与非异常),与原始专业化的现有组合爆炸相互作用很严重.

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

在此期间,您可以使用工具来执行您想要的操作.我得到你喜欢我们提供最后一英里你(和,其次,你的要求是真正的"你为什么不干脆放弃对检查的异常已经是"一个自动精简含蓄的要求),但我认为目前的状态让你完成了你的工作.

  • @LyubomyrShaydariv功能界面就像任何其他界面一样.您可以通过lambda表达式或手动实现它.如果要在未声明抛出该异常的接口方法的实现中抛出已检查的异常,则会破坏编程语言的语义.您正在抛出处理接口实现的代码所不期望的异常.我不认为那是好的.我需要一个更详细的例子来证明你对我有意义.也许你应该为此创建另一个讨论. (3认同)
  • 谢谢回复.坦率地说,我希望lambda表达式将被检查 - 异常友好,并且已检查异常的范围将由lambdas传递,因为它们纯粹是编译器驱动的特性.但没有运气. (2认同)
  • 好吧,所以我目前唯一看到的,如果lambda表达式被检查 - 异常友好,是:`try {iDontHaveThrowsDeclared(x - > someIOOperation(x))} catch(IOException){...}`将无法编译,因为`catch`块中的`IOException`已知是一个已检查的,而`iDontHaveThrowsDeclared`则没有`throws`子句.是的,它打破了. (2认同)

mic*_*cha 5

2015年9月:

你可以使用ET.ET是一个用于异常转换/转换的小型Java 8库.

使用ET,您可以写:

super(() -> et.withReturningTranslation(() -> DriverManager.getConnection(url)), maxConnections);
Run Code Online (Sandbox Code Playgroud)

多行版本:

super(() -> {
  return et.withReturningTranslation(() -> DriverManager.getConnection(url));
}, maxConnections);
Run Code Online (Sandbox Code Playgroud)

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

ExceptionTranslator et = ET.newConfiguration().done();
Run Code Online (Sandbox Code Playgroud)

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

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


Grz*_*rek 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);
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

}

http://github.com/pivovarit/ throwing-function