我今天高高兴兴,想写个简单的统计一个项目下有多少行代码的小程序,因而咔咔的写下: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);
{ 题外话开始:git
Files.walk(Path)
在 JDK1.8 时添加,深度优先遍历一个 Path
(目录),返回这个目录下全部的 Path
(目录和文件),经过 Stream<Path>
返回;Files.lines(Path)
也是在 JDK1.8 时添加,功能是返回指定 Path
(文件)中全部的行,经过 Stream<String>
返回题外话结束 }github
而后,编译不过 —— 由于 Files.lines(Path)
会抛出 IOException
,若是要编译经过,得这样写:express
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:经过新建一个方法( :) 无奈可是纯洁的微笑)app
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:将会抛出异常的函数进行包装,使其不抛出受检异常ui
若是一个 FunctionInterface 的方法会抛出受检异常(好比 Exception
),那么该 FunctionInterface 即可以做为会抛出受检异常的 Lambda 的目标类型。编码
咱们定义以下一个受检的 FunctionInterface:spa
@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>
没有抛出异常:
那咱们如何使用 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
这种方式更为通用 —— 相似的,咱们能够包装 CheckedConsumer
为 java.util.function.Consumer
,包装 CheckedSupplier
为 java.util.function.Suppiler
,CheckedBiFunction
为 java.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
接口的代码可见:Attempt.java
就我我的观点而言,我真的不喜欢 Java 中的受检(Checked)异常,我认为全部的异常都应该是非受检(Unchecked)的 —— 由于一段代码若是会产生异常,咱们天然会去解决这个问题直到其不抛出异常或者捕获这个异常并作对应处理 —— 强制性的要求编码人员捕获异常,带来的更多的是编码上的不方便和代码可读性的下降(由于冗余)。不过既然受检异常已是 Java 中的客观存在的事物,所谓“道高一尺,魔高一丈” —— 老是会有办法来应对。