Java8:当 Lambda 遇上受检异常

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

.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 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 getLines(Path file) {

try {

return Files.lines(file);

} catch (IOException ex) {

ex.printStackTrace(System.err);

return Stream.empty();

}

}

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

解决方法二

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

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

我们定义如下一个受检的 FunctionInterface

@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();

写在最后

可能有人会问我为什么愿意去花时间帮助大家实现求职梦想,因为我一直坚信时间是可以复制的。我牺牲了自己的大概十个小时写了这片文章,换来的是成千上万的求职者节约几天甚至几周时间浪费在无用的资源上。

复习一周,字节跳动三场技术面+HR面,不小心拿了offer

复习一周,字节跳动三场技术面+HR面,不小心拿了offer

上面的这些(算法与数据结构)+(Java多线程学习手册)+(计算机网络顶级教程)等学习资源
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
在最后

可能有人会问我为什么愿意去花时间帮助大家实现求职梦想,因为我一直坚信时间是可以复制的。我牺牲了自己的大概十个小时写了这片文章,换来的是成千上万的求职者节约几天甚至几周时间浪费在无用的资源上。

[外链图片转存中…(img-lZ7YtDlt-1714437012533)]

[外链图片转存中…(img-jfwrtSeg-1714437012534)]

上面的这些(算法与数据结构)+(Java多线程学习手册)+(计算机网络顶级教程)等学习资源
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值