Java8:当 Lambda 赶上受检异常

Java8:当 Lambda 赶上受检异常

前言

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

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> 返回git

而后,编译不过 —— 由于 Files.lines(Path) 会抛出 IOException,若是要编译经过,得这样写:github

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 expressionLambda,咱们须要解决的办法。面试

解决方案

解决方法一

经过新建一个方法( :) 无奈可是纯洁的微笑)算法

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)express

解决方法二

将会抛出异常的函数进行包装,使其不抛出受检异常编程

若是一个 FunctionInterface 的方法会抛出受检异常(好比 Exception),那么该 FunctionInterface 即可以做为会抛出受检异常的 Lambda 的目标类型。小程序

咱们定义以下一个受检的 FunctionInterfacebash

@FunctionalInterface
interface CheckedFunction<T, R> {
    R apply(T t) throws Throwable;
}

复制代码

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

java.util.function.Function
复制代码

那咱们如何使用 CheckedFunction 到流式操做的 Lambda 中呢? 首先咱们定义一个 Attempt 接口,它的 apply 静态方法提供将 CheckedFunction 包装为 Function 的功能:

public interface Attempt {

   static <T, R> Function<T, R> apply(CheckedFunction<T, R> function) {
        Objects.requireNonNull(function);
        
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        };
    }

}
复制代码

而后在原先的代码中,咱们使用 Attempt.apply 方法来对会抛出受检异常的 Lambda 进行包装:

long count = Files.walk(Paths.get("D:/Test"))              // 得到项目目录下的全部文件
                .filter(file -> !Files.isDirectory(file))          // 筛选出文件
                .filter(file -> file.toString().endsWith(".java")) // 筛选出 java 文件
        
                .flatMap(Attempt.apply(file -> Files.lines(file)))        // 将 会抛出受检异常的 Lambda 包装为 抛出非受检异常的 Lambda
        
                .filter(line -> !line.trim().isEmpty())            // 过滤掉空行
                .count();

System.out.println("代码行数:" + count);
复制代码

此时,咱们即可以选择是否去捕获异常(RuntimeException)。这种解决方法下,咱们通常不关心抛出异常的状况 —— 好比本身写的小例子,抛出了异常程序就该终止;或者你知道这个 Lambda 确实 100% 不会抛出异常。

不过我更倾向于抛出异常时,咱们来指定处理的方式:

static <T, R> Function<T, R> apply(CheckedFunction<T, R> function, Function<Throwable, R> handler) {
    Objects.requireNonNull(function);
    Objects.requireNonNull(handler);

    return t -> {
        try {
            return function.apply(t);
        } catch (Throwable e) {
            return handler.apply(e);
        }
    };
}
复制代码

好比咱们前面的例子,若是 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(TryTo.apply(file -> Files.lines(file), ex -> Stream.empty()))
        
                .filter(line -> !line.trim().isEmpty())            // 过滤掉空行
                .count();

System.out.println("代码行数:" + count);

复制代码

使用 CheckedFunction这种方式更为通用 —— 相似的,咱们能够包装 CheckedConsumerjava.util.function.Consumer,包装 CheckedSupplierjava.util.function.SuppilerCheckedBiFunctionjava.util.function.BiFunction 等:

public interface Attempt {

    ......
        
    /**
     * 包装受检的 Consumer
     */
    static <T> Consumer<T> accept(CheckedConsumer<T> consumer) {
        Objects.requireNonNull(consumer);

        return t -> {
            try {
                consumer.accept(t);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        };
    }

    /**
     * 包装受检的 Consumer,并自定义异常处理
     */
    static <T> Consumer<T> accept(CheckedConsumer<T> consumer, Consumer<Throwable> handler) {
        Objects.requireNonNull(consumer);
        Objects.requireNonNull(handler);

        return t -> {
            try {
                consumer.accept(t);
            } catch (Throwable e) {
                handler.accept(e);
            }
        };
    }
}
复制代码

本文 Attempt 接口的代码:公众号回复:20191015Attempt

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

推荐

大厂笔试内容集合(内有详细解析) 持续更新中....

ProcessOn是一个在线做图工具的聚合平台~

文末

欢迎关注我的微信公众号:Coder编程 欢迎关注Coder编程公众号,主要分享数据结构与算法、Java相关知识体系、框架知识及原理、Spring全家桶、微服务项目实战、DevOps实践之路、每日一篇互联网大厂面试或笔试题以及PMP项目管理知识等。更多精彩内容正在路上~ 新建了一个qq群:315211365,欢迎你们进群交流一块儿学习。谢谢了!也能够介绍给身边有须要的朋友。

文章收录至 Github: github.com/CoderMerlin… Gitee: gitee.com/573059382/c… 欢迎关注并star~

微信公众号
相关文章
相关标签/搜索