Java8(1):当 Lambda 遇上受检异常[通俗易懂]

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 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> 没有抛出异常:

java.util.function.Function

那我们如何使用 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 中的客观存在的事物,所谓“道高一尺,魔高一丈” —— 总是会有办法来应对。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/108717.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 集成环境哪个好?四大PHP集成开发环境比较

    集成环境哪个好?四大PHP集成开发环境比较http://www.5icool.org/a/201505/a11537.html专注了这么些年技术,没有养成记录和积累的习惯。如今乐于开源和分享经验,却停笔踌躇,不知该从何处说起。开通博客也有一段时间了,也没能写出一篇像样的文章,想了很久,觉得还是应该循序渐进,从搭建和配置开发、调试环境开始。主流的PHP集成开发环境(IntegratedDevelopmentEnvironment…

    2022年6月28日
    41
  • sql中declare的用法_sql局部变量

    sql中declare的用法_sql局部变量换工作了,以后主要和SqlServer打交道了,仿佛回到了大学,不知道学校的饭还是那么好吃又便宜吗?北京的饭好贵;不知道门口哪家板面的生意是不是还是那么红火,好想再去吃一碗。。。咳咳,不多说了,直接进入主题declare这个类型,其实可以理解为Java里面的public类型变量,全局有效,当然非要较真的话,我觉得归到protected类也可以(不理解的话不要看后半段,只是为了严谨)Java修饰符 public:对所有类可见。使用对象:类、接口、变量、方法 protect..

    2022年8月20日
    8
  • C语言:判断回文字符串的两种简单方法

    C语言:判断回文字符串的两种简单方法我的机器学习教程「美团」算法工程师带你入门机器学习已经开始更新了,欢迎大家订阅~任何关于算法、编程、AI行业知识或博客内容的问题,可以随时扫码关注公众号「图灵的猫」,加入”学习小组“,沙雕博主在线答疑~此外,公众号内还有更多AI、算法、编程和大数据知识分享,以及免费的SSR节点和学习资料。其他平台(知乎/B站)也是同名「图灵的猫」,不要迷路哦~之前写…

    2022年6月6日
    19
  • 小树311_森林小道

    小树311_森林小道原题链接森森开了一家快递公司,叫森森快递。因为公司刚刚开张,所以业务路线很简单,可以认为是一条直线上的N个城市,这些城市从左到右依次从0到(N−1)编号。由于道路限制,第i号城市(i=0,⋯,N−2)与第(i+1)号城市中间往返的运输货物重量在同一时刻不能超过C​i​​ 公斤。公司开张后很快接到了Q张订单,其中j张订单描述了某些指定的货物要从S​j​​ 号城市运输到T​j​​ 号城市。这里我们简单地假设所有货物都有无限货源,森森会不定时地挑选其中一部分货物进行运输。安全起见,这些货物不会在中

    2022年8月9日
    5
  • meta分析一般步骤

    Meta分析总体可分为以下几步:选题,文献检索、数据提取、质量评估、数据整合及结果解读。一,选题对一些大样本,多中心临床合作已经得到明确结论的的,没必要做meta分析。二、文献检索在制定文献检索策略时,总体的要求就是查全和查准。需要考虑如下几个方面:1.圈定搜索数据库(外文有:MEDLINE、theCochranelibrary、医学文摘、TOXLINE、OVI…

    2022年4月9日
    59
  • mysql创建前缀索引

    mysql创建前缀索引ALTERtable表名addindextitle_pre(列名(100))列名后面的数字代表前缀的长度,前缀长度并不是越长越好,这里涉及到一个选择性问题,selectcount(distinct列名)/count(*)asa,COUNT(DISTINCTleft(列名,100))asb,COUNT(DISTINCTleft(列名,110))ascfrom表名来查…

    2022年5月10日
    61

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号