资讯专栏INFORMATION COLUMN

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

lavor / 2796人阅读

摘要:如果的流式操作中多几个需要抛出受检异常的情况,那代码真是太不直观了,所以为了的,我们需要解决的办法。不过既然受检异常已经是中的客观存在的事物,所谓道高一尺,魔高一丈总是会有办法来应对。

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

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

Files.lines(Path) 也是在 JDK1.8 时添加,功能是返回指定 Path (文件)中所有的行,通过 Stream 返回

题外话结束 }

然后,编译不过 —— 因为 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 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 {
    R apply(T t) throws Exception;
}

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

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

public class Try {

    public static  Function of(UncheckedFunction mapper) {
        Objects.requireNonNull(mapper);
        return t -> {
            try {
                return mapper.apply(t);
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        };
    }

    @FunctionalInterface
    public static interface UncheckedFunction {

        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  Function of(
        UncheckedFunction 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

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://www.ucloud.cn/yun/66363.html

相关文章

  • Introducing FP in Java8

    摘要:函数副作用会给程序设计带来不必要的麻烦,引入潜在的,并降低程序的可读性。所以只能采用这种曲线救国的方式。则是把这种曲线救国拿到了台面上,并昭告天下,同时还对提供了一些语法支持。是自由变量,提供执行上下文,触发闭包执行。 背景 自从2013年放弃了Java就再也没有碰过。期间Java还发布了重大更新:引入lambda,但是那会儿我已经玩了一段时间Scala,对Java已经瞧不上眼。相比S...

    Prasanta 评论0 收藏0
  • Jumping with Try

    摘要:解决思路或生产对象,扮演生产者的角色而消费对象,扮演消费者的角色。正常情况下它们生产对象,而异常情况下,则抛出异常。重构的思路在于将异常处理更加明晰化,让生产者与消费者之间的关系流水化。容器化其中,与包内私有,对外不公开。 场景 以一个简化了的用户登录的鉴权流程,流程大体如下: 首先尝试本站鉴权,如果失败,再尝试twiter的方式恢复; 之后再进行Two Factor认证; 快速实...

    Java3y 评论0 收藏0
  • Effective Java 3rd.Edition 翻译

    摘要:推荐序前言致谢第一章引言第二章创建和销毁对象第项用静态工厂方法代替构造器第项遇到多个构造器参数时要考虑使用构建器第项用私有构造器或者枚举类型强化属性第项通过私有构造器强化不可实例化的能力第项优先考虑依赖注入来引用资源第项避免创建不必要的对象 推荐序 前言 致谢 第一章 引言 第二章 创建和销毁对象 第1项:用静态工厂方法代替构造器 第2项:遇到多个构造器参数时要考虑使用构建器 第...

    KoreyLee 评论0 收藏0
  • Effective Java 第三版 全文翻译

    摘要:本章中的大部分内容适用于构造函数和方法。第项其他方法优先于序列化第项谨慎地实现接口第项考虑使用自定义的序列化形式第项保护性地编写方法第项对于实例控制,枚举类型优先于第项考虑用序列化代理代替序列化实例附录与第版中项目的对应关系参考文献 effective-java-third-edition 介绍 Effective Java 第三版全文翻译,纯属个人业余翻译,不合理的地方,望指正,感激...

    galois 评论0 收藏0
  • 《java 8 实战》读书笔记 -第三章 Lambda表达式

    摘要:利用前面所述的方法,这个例子可以用方法引用改写成下面的样子构造函数引用对于一个现有构造函数,你可以利用它的名称和关键字来创建它的一个引用。 第三章 Lambda表达式 函数式接口 函数式接口就是只定义一个抽象方法的接口,哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。 常用函数式接口 showImg(https://segmentfault.com/img...

    whinc 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<