当 Lambda 表达式遇上 Checked Exception
Lambda 表达式在编码时带来了极简的体验,但是当 Lambda 表达式中出现 Checked Exception (受检异常)时,为了要处理异常,整个 Lambda 表达式就会变冗长,与 Lambda 表达式本身的风格格格不入,本文将讨论几种可能的解决方案。
一块简单的统计文件夹下文本行数的代码:
1 | long count = Files.walk(Paths.get("D:/Test")) // 获得项目目录下的所有目录及文件 |
显然,由于 Files.lines(file)
会抛出受检异常 IOException
,当代码敲到这一行时,Idea 无情地给这一行代码加上了红色波浪线——未处理的受检异常,要让代码可以编译成功,要这么写:
1 | long count = Files.walk(Paths.get("D:/Test")) // 获得项目目录下的所有文件 |
Lambda 代码中这么大一块异常处理显得尤其突兀,如果在整个 Stream 操作的过程中再多几个这样需要处理异常的情况,那真是把 Lambda 的简洁性丢得一干二净。
解决方法1
既然是因为 Files.lines(file)
的异常没有处理才引发的问题,那就写个方法,将没有处理异常的方法包装起来,然后在方法内部处理异常:
1 | long count = Files.walk(Paths.get("D:/Test")) // 获得项目目录下的所有文件 |
这种方法下,我们需要处理受检异常 —— 即在程序抛出异常的时候,我们需要告诉程序怎么去做(getLines 方法中抛出异常时我们输出了异常,并返回一个空的 Stream)
解决方法2
如果一个 FunctionInterface 的方法会抛出受检异常(比如 Exception),那么该 FunctionInterface 便可以作为会抛出受检异常的 Lambda 的目标类型。(说人话:定义一个抛出异常的函数式接口替代原有的函数式接口)
定义如下一个 FunctionInterface:
1 |
|
那么该 FunctionInterface 便可以作为类似于 file -> File.lines(file) 这类会抛出受检异常的 Lambda 的目标类型,此时 Lambda 中并不需要捕获异常(因为目标类型的 apply 方法已经将异常抛出了)—— 之所以原来的 Lambda 需要捕获异常,就是因为在流式操作 flatMap 中使用的 java.util.function 包下的 Function<T, R> 没有抛出异常。
再定义一个 Try 类,它的 of 方法提供将 UncheckedFunction 包装为 Function 的功能:
1 | public class Try { |
在原先的代码中,使用 Try.of 方法来对会抛出受检异常的 Lambda 进行包装:
1 | long count = Files.walk(Paths.get("D:/Test")) // 获得项目目录下的所有文件 |
这种解决方法下,我们一般不关心抛出异常的情况 —— 比如自己写的小例子,抛出了异常程序就该终止;或者你知道这个 Lambda 确实 100% 不会抛出异常。
从模块整体对外表现的稳定性来看,在函数式接口的参数处指定一个默认值,在出错时返回一个默认值可能是更好的解决方法:
1 | public class Try { |
针对统计文本行数的例子,在访问文件出错时,不妨使用“鸵鸟算法”,假装这个文件不存在,让其返回默认值:一个空的 Stream 对象。
小结
使用 UncheckedFunction 这种方式更为通用,我们可以在更多的地方将 UncheckedFunction 包装成 java.util.function.Function。类似的,我们可以包装 UncheckedConsumer 为 java.util.function.Consumer,包装 UncheckedSupplier 为 Suppiler,UncheckedBiFunction 为 BiFunction 等。