我今天高高興興,想寫個簡單的統計一個項目下有多少行代碼的小程序,因而咔咔的寫下: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 中的客觀存在的事物,所謂「道高一尺,魔高一丈」 —— 老是會有辦法來應對。