Java8(1):当 Lambda 遇上受检异常

发布时间:2019-11-20 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了Java8(1):当 Lambda 遇上受检异常脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

我今天高高兴兴,想写个简单的统计一个项目下有多少行代码的小程序,于是咔咔的写下:

long count = Files.walk(Paths.get("D:/test"))                      // 获得项目目录下的所有目录及文件                 .filter(file -> !Files.isDirectory(file))          // 筛选出文件                 .filter(file -> file.toString().endsWith(".java")) // 筛选出 java 文件                 .flatMap(file -> Files.lines(file))                // 按行获得文件中的文本                 .filter(line -> !line.trim().isEmpty())            // 过滤掉空行                 .count();  System.out.println("代码行数:" + count);

{ 题外话开始

  • Files.walk(Path) 在 JDK1.8 时添加,深度优先遍历一个 Path (目录),返回这个目录下所有的 Path(目录和文件),通过 Stream<Path> 返回;
  • Files.lines(Path) 也是在 JDK1.8 时添加,功能是返回指定 Path (文件)中所有的行,通过 Stream<String> 返回

题外话结束 }

然后,编译不过 —— 因为 Files.lines(Path) 会抛出 IOException,如果要编译通过,得这样写:

long count = Files.walk(Paths.get("D:/Test"))                      // 获得项目目录下的所有文件                 .filter(file -> !Files.isDirectory(file))          // 筛选出文件                 .filter(file -> file.toString().endsWith(".java")) // 筛选出 java 文件                 .flatMap(file -> {                     try {                         return Files.lines(file);                     } catch (IOException ex) {                         ex.printStackTrace(System.err);                         return Stream.empty();                     // 抛出异常时返回一个空的 Stream                     }                 })                                                 // 按行获得文件中的文本                 .filter(line -> !line.trim().isEmpty())            // 过滤掉空行                 .count();  System.out.println("代码行数:" + count);

我的天,这个时候我强迫症就犯了 —— 因为这样的 Lambda 不是 one-liner exPression,不够简洁。如果 Stream 的流式操作中多几个需要抛出受检异常的情况,那代码真是太不直观了,所以为了 one-liner expression 的 Lambda,我们需要解决的办法。

解决方法1:通过新建一个方法(:) 无奈但是纯洁的微笑)

public static void main(String[] args) throws Exception {     long count = Files.walk(Paths.get("D:/Test"))                       // 获得项目目录下的所有文件                     .filter(file -> !Files.isDirectory(file))           // 筛选出文件                     .filter(file -> file.toString().endsWith(".java"))  // 筛选出 java 文件                     .flatMap(file -> getLines(file))                    // 按行获得文件中的文本                     .filter(line -> !line.trim().isEmpty())             // 过滤掉空行                     .count();      System.out.println("代码行数:" + count); }  private static Stream<String> getLines(Path file) {     try {         return Files.lines(file);     } catch (IOException ex) {         ex.printStackTrace(System.err);         return Stream.empty();     } }

这种解决方法下,我们需要处理受检异常 —— 即在程序抛出异常的时候,我们需要告诉程序怎么去做(getLines 方法中抛出异常时我们输出了异常,并返回一个空的 Stream

解决方法2:将会抛出异常的函数进行包装,使其不抛出受检异常


如果一个 FunctionInterface 的方法会抛出受检异常(比如 Exception),那么该 FunctionInterface 便可以作为会抛出受检异常的 Lambda 的目标类型


我们定义如下一个 FunctionInterface

@FunctionalInterface interface @H_304_305@UncheckedFunction<T, R> {     R apply(T t) throws Exception; }

那么该 FunctionInterface 便可以作为类似于 file -> File.lines(file) 这类会抛出受检异常的 Lambda 的目标类型,此时 Lambda 中并不需要捕获异常(因为目标类型的 apply 方法已经将异常抛出了)—— 之所以原来的 Lambda 需要捕获异常,就是因为在流式操作 flatMap 中使用的 java.util.function 包下的 Function<T, R> 没有抛出异常:

Java8(1):当 Lambda 遇上受检异常

那我们如何使用 UncheckedFunction 到流式操作的 Lambda 中呢?
首先我们定义一个 Try 类,它的 of 方法提供将 UncheckedFunction 包装为 Function 的功能:

public class Try {      public static <T, R> Function<T, R> of(UncheckedFunction<T, R> mapper) {         Objects.requireNonNull(mapper);         return t -> {             try {                 return mapper.apply(t);             } catch (Exception ex) {                 throw new RuntimeException(ex);             }         };     }      @FunctionalInterface     public static interface UncheckedFunction<T, R> {          R apply(T t) throws Exception;     } }

然后在原先的代码中,我们使用 Try.of 方法来对会抛出受检异常的 Lambda 进行包装:

long count = Files.walk(Paths.get("D:/Test"))              // 获得项目目录下的所有文件                 .filter(file -> !Files.isDirectory(file))          // 筛选出文件                 .filter(file -> file.toString().endsWith(".java")) // 筛选出 java 文件                          .flatMap(Try.of(file -> Files.lines(file)))        // 将 会抛出受检异常的 Lambda 包装为 抛出非受检异常的 Lambda                          .filter(line -> !line.trim().isEmpty())            // 过滤掉空行                 .count();  System.out.println("代码行数:" + count);

此时,我们便可以选择是否去捕获异常(RuntimeException)。这种解决方法下,我们一般不关心抛出异常的情况 —— 比如自己写的小例子,抛出了异常程序就该终止;或者知道这个 Lambda 确实 100% 不会抛出异常。

我更倾向于一种指定默认值的包装方法,即如果抛出异常,那么就返回默认值:

public static <T, R> Function<T, R> of(         UncheckedFunction<T, R> mapper, R defaultR) {     Objects.requireNonNull(mapper);     return t -> {         try {             return mapper.apply(t);         } catch (Exception ex) {             System.err.println(ex.getMessage());             return defaultR;         }     }; }

比如我们前面的例子,如果 file -> Files.lines(file) 抛出异常了,说明在访问 file 类的时候出了问题,我们可以就假设这个文件的行数为 0 ,那么默认值就是个空的 Stream<String>

long count = Files.walk(Paths.get("D:/Test"))              // 获得项目目录下的所有文件                 .filter(file -> !Files.isDirectory(file))          // 筛选出文件                 .filter(file -> file.toString().endsWith(".java")) // 筛选出 java 文件                          .flatMap(Try.of(file -> Files.lines(file), Stream.empty()))                          .filter(line -> !line.trim().isEmpty())            // 过滤掉空行                 .count();  System.out.println("代码行数:" + count);

使用 UncheckedFunction 这种方式更为通用,我们可以在更多的地方将 UncheckedFunction 包装成 java.util.function.Function。类似的,我们可以包装 UncheckedConsumerjava.util.function.Consumer,包装 UncheckedSupplierSuppilerUncheckedBiFunctionBiFunction 等。


就我个人观点而言,我真的不喜欢 Java 中的受检(Checked)异常,我认为所有的异常都应该是非受检(Unchecked)的 —— 因为一段代码如果会产生异常,我们自然会去解决这个问题直到其不抛出异常或者捕获这个异常并做对应处理 —— 强制性的要求编码人员捕获异常,带来的更多的是编码上的不方便和代码可读性的降低(因为冗余)。不过既然受检异常已经是 Java 中的客观存在的事物,所谓“道高一尺,魔高一丈” —— 总是会有办法来应对。

脚本宝典总结

以上是脚本宝典为你收集整理的Java8(1):当 Lambda 遇上受检异常全部内容,希望文章能够帮你解决Java8(1):当 Lambda 遇上受检异常所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。