Java8 Lambda 表达式与 Checked Exception

当我们在使用 Java 8 的 Lambda 表达式时,表达式内容需要抛出异常,也许还会想当然的让当前方法再往外抛来解决编译问题,如下面的代码

main() 方法抛出 Exception 还是不解决决编译错误,仍然提示 "Unhandled exception: java.io.FileNotFoundException"。

因为我们可能保持着惯性思维,忽略了 Lambda 本身就是一个功能性接口方法的实现,所以把上面的代码还原为匿名类的方式

那么对于上面那种情况应该如何处理呢?

就地处决或转换为 unchecked 异常

现在我们就不会让 foo() 方法去抛出一个异常来捕获 new FileInputStream(s).close() 这一行可能出现的异常,一定是会让 accept() 方法来向外层抛异常,正是因为 Consumer 定义的 accept() 方法定义不抛异常,所以若是用 IntelliJ IDEA 的话, 它会提示我们把会产生异常的那行 catch 起来,像下面那样

这种情况似乎只能这样把 checked exception 转换为 unchecked exception 了。对于上面的改动,想要 catch 那个 RuntimeException 的话也没问题

使用声明了异常的 SAM,可在外层方法继续抛出

如果不想要捕获异常再转换为 unchecked exception 的话,那就不能用 Java 8 内置的 Consumer 接口了,需要有一个声明抛出 Exception 的  accept() 方法的 Consumer. 比如下面的定义的 MyConsumer, 它的 accept() 方法抛出异常,代码如下:

上面的 foo() 和 client() 方法就可以声明抛出由 new FileInputStream() 产生的异常,而不需要进行异常转换。

并发操作是 Lambda 的异常处理

在单线程模式下,异常还是容易处理,有异常时在当前线程的异常栈中能查找到, 而对于其他线程中抛出的异常在当前线程中是无法捕获到的,就像下面的尝试是不会成功的

在我的测试下控制台的输出类似如下

Exception in thread "Thread-0" java.lang.RuntimeException: Something wrong
    at cc.unmi.TestLambdaException.lambda$bar$1(TestLambdaException.java:41)
    at java.lang.Thread.run(Thread.java:745)

那么 Java 8 的 parallelStream() 会把任务分配到其他线程去执行,是不是也无法在调用者线程上捕获到 parallelStream().forEach(Consumer) 中抛出的异常呢?不是的,可正常捕获,因为 Java 8 的 ForkJoinTask 有进行特殊的处理,会在子线程发生异常时把子线程的异常附着到调用者线程上去。我们来运行下面的代码

多次运行上面的代码可以收到两种情况的输出

第一种, 只有一层 RuntimeException("file not found"):

第二种,有两层的 RuntimeException("file not found")

总之,在使用 Java 8 集合框架的 parallelStream() 可以正常的在启动线程中捕获到 Lambda 表达式中产生的异常。

由上图中可看到起关键作用的就是 ForkJoinTask 的 invoke(),  reportException(), 和  getThrowableException() 方法,可以大概看下相关的 java.util.concurrent.ForkJoinTask  代码片断:

这可以作为我们在实际的多线程应用中异常处理的一个参考。

类别: Java8. 标签: , , . 阅读(1,773). 订阅评论. TrackBack.

Leave a Reply

Be the First to Comment!

avatar