为什么在Java 8 lambda表达式中不能抛出异常?

50

我升级到了Java 8并尝试用新的lambda表达式替换一个遍历Map的简单迭代。这个循环搜索null值,如果找到则抛出异常。旧的Java 7代码如下:

for (Map.Entry<String, String> entry : myMap.entrySet()) {
    if(entry.getValue() == null) {
        throw new MyException("Key '" + entry.getKey() + "' not found!");
    }
}

我尝试将其转换为Java 8,代码如下:

myMap.forEach((k,v) -> {
    if(v == null) {
        // OK
        System.out.println("Key '" + k+ "' not found!");

        // NOK! Unhandled exception type!
        throw new MyException("Key '" + k + "' not found!");
    }
});

谁能解释一下为什么这里不允许使用throw语句,以及如何进行更正?

Eclipse的快速修复建议对我来说看起来不正确...它只是用try-catch块包围了throw语句:

myMap.forEach((k,v) -> {
    if(v == null) {
        try {
            throw new MyException("Key '" + k + "' not found!");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
});

14
有一个非常简单的答案:lambda表达式是一种实现函数式接口的简洁方式。就像任何其他实现接口的方式一样,您必须符合接口契约--这意味着您只能抛出目标类型允许的异常。这里没有什么魔法。 - Brian Goetz
2个回答

68
您不允许抛出已检查的异常,因为java.util.function.BiConsumer<T, U>接口中的accept(T t, U u)方法在其throws子句中没有声明任何异常。而且,正如您所知,Map#forEach使用了这种类型。
public interface Map<K, V> {

    default void forEach(BiConsumer<? super K, ? super V> action) { ... }

}                        |
                         |
                         V
@FunctionalInterface
public interface BiConsumer<T, U> {

    void accept(T t, U u); // <-- does throw nothing

}

当我们谈论检查异常时,确实是这样的。但你仍然可以抛出未经检查的异常(例如java.lang.IllegalArgumentException):

new HashMap<String, String>()
    .forEach((a, b) -> { throw new IllegalArgumentException(); });

4
我基本同意这个答案。Java文档中让人困惑的地方在于“由操作引发的异常将被传递给调用者。” 顺便说一下,在Lambda内抛出异常是允许的,只是在这种特定情况下不允许。 - micker
@micker 值得一提的是:该语句仅附加于Iterable<T>接口的forEach默认成员方法。主要的Stream/Consumer forEach没有关于已检查异常行为的任何文档(尽管可以从函数签名中推断出一些结论)。 - Ti Strga
@andrew-tobilko,谢谢你的回答!我想知道在Java中是否有类似的情况,即Optional.ifPresentOrElse,我们也无法抛出任何已检查的异常。 - hd84335

30

您可以在lambda表达式中抛出异常。

lambda表达式允许抛出与其实现的函数接口相同的异常。

如果函数接口的方法没有throws子句,则lambda不能抛出CheckedExceptions。(您仍然可以抛出RuntimeExceptions)。


在您的特定情况下,Map.forEach使用BiConsumer作为参数,BiConsumer定义如下:

public interface BiConsumer<T, U> {
    void accept(T t, U u);
}

这个函数式接口的lambda表达式不能抛出Checked异常。


java.util.function包中定义的函数式接口的方法不会抛出异常,但是你可以使用其他函数式接口或创建自己的函数式接口来抛出异常,例如给定以下接口:

public interface MyFunctionalInterface<T> {
    void accept(T t) throws Exception;
}

以下代码是合法的:
MyFunctionalInterface<String> f = (s)->throw new Exception("Exception thrown in lambda"); 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接