我今天高高興興询枚,想寫個簡單的統(tǒng)計一個項目下有多少行代碼的小程序识腿,于是咔咔的寫下:
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("代碼行數(shù):" + count);
{ 題外話開始:
Files.walk(Path)
在 JDK1.8 時添加赁豆,深度優(yōu)先遍歷一個Path
(目錄)汤功,返回這個目錄下所有的Path
(目錄和文件),通過Stream<Path>
返回导坟;Files.lines(Path)
也是在 JDK1.8 時添加,功能是返回指定Path
(文件)中所有的行圈澈,通過Stream<String>
返回
題外話結(jié)束 }
然后惫周,編譯不過 —— 因為 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("代碼行數(shù):" + 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("代碼行數(shù):" + 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:將會拋出異常的函數(shù)進行包裝匾二,使其不拋出受檢異常
如果一個 FunctionInterface 的方法會拋出受檢異常(比如 Exception
)哮独,那么該 FunctionInterface 便可以作為會拋出受檢異常的 Lambda 的目標(biāo)類型。
我們定義如下一個受檢的 FunctionInterface:
@FunctionalInterface
interface CheckedFunction<T, R> {
R apply(T t) throws Throwable;
}
那么該 FunctionalInterface 便可以作為類似于 file -> File.lines(file)
這類會拋出受檢異常的 Lambda 的目標(biāo)類型察藐,此時 Lambda 中并不需要捕獲異常(因為目標(biāo)類型的 apply
方法已經(jīng)將異常拋出了)—— 之所以原來的 Lambda 需要捕獲異常皮璧,就是因為在流式操作 flatMap
中使用的 java.util.function
包下的 Function<T, R>
沒有拋出異常:
那我們?nèi)绾问褂?CheckedFunction
到流式操作的 Lambda 中呢?
首先我們定義一個 Attempt
接口分飞,它的 apply
靜態(tài)方法提供將 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("代碼行數(shù):" + count);
此時,我們便可以選擇是否去捕獲異常(RuntimeException
)譬猫。這種解決方法下讯檐,我們一般不關(guān)心拋出異常的情況 —— 比如自己寫的小例子,拋出了異常程序就該終止染服;或者你知道這個 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 類的時候出了問題,我們可以就假設(shè)這個文件的行數(shù)為 0 秉颗,那么默認(rèn)值就是個空的 Stream<String>
(當(dāng)然你也可以選擇順手記錄一下異常):
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("代碼行數(shù):" + 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)異常梢灭,我認(rèn)為所有的異常都應(yīng)該是非受檢(Unchecked)的 —— 因為一段代碼如果會產(chǎn)生異常夷家,我們自然會去解決這個問題直到其不拋出異常或者捕獲這個異常并做對應(yīng)處理 —— 強制性的要求編碼人員捕獲異常敏释,帶來的更多的是編碼上的不方便和代碼可讀性的降低(因為冗余)库快。不過既然受檢異常已經(jīng)是 Java 中的客觀存在的事物,所謂“道高一尺钥顽,魔高一丈” —— 總是會有辦法來應(yīng)對义屏。