這樣也行,在lambda表達(dá)式中優(yōu)雅的處理checked exception
簡介
最近發(fā)現(xiàn)很多小伙伴還不知道如何在lambda表達(dá)式中優(yōu)雅的處理checked exception,所以今天就重點(diǎn)和大家來探討一下這個(gè)問題。
lambda表達(dá)式本身是為了方便程序員書寫方便的工具,使用lambda表達(dá)式可以讓我們的代碼更加簡潔。
可能大多數(shù)小伙伴在使用的過程中從來沒有遇到過里面包含異常的情況,所以對(duì)這種在lambda表達(dá)式中異常的處理可能沒什么經(jīng)驗(yàn)。
不過沒關(guān)系,今天我們就來一起探討一下。
lambda表達(dá)式中的checked exception
java中異常的類型,大家應(yīng)該是耳熟能詳了,具體而言可以有兩類,一種是checked exception, 一種是unchecked exception。
所謂checked exception就是需要在代碼中手動(dòng)捕獲的異常。unchecked exception就是不需要手動(dòng)捕獲的異常,比如運(yùn)行時(shí)異常。
首先我們定義一個(gè)checked exception,直接繼承Exception就好了:
public class MyCheckedException extends Exception{
@java.io.Serial
private static final long serialVersionUID = -1574710658998033284L;
public MyCheckedException() {
super();
}
public MyCheckedException(String s) {
super(s);
}
}
接下來我們定義一個(gè)類,這個(gè)類中有兩個(gè)方法,一個(gè)拋出checked exception,一個(gè)拋出unchecked exception:
public class MyStudents {
public int changeAgeWithCheckedException() throws MyCheckedException {
throw new MyCheckedException();
}
public int changeAgeWithUnCheckedException(){
throw new RuntimeException();
}
}
好了,我們首先在lambda表達(dá)式中拋出CheckedException:
public static void streamWithCheckedException(){
Stream.of(new MyStudents()).map(s->s.changeAgeWithCheckedException()).toList();
}
這樣寫在現(xiàn)代化的IDE中是編譯不過的,它會(huì)提示你需要顯示catch住CheckedException,所以我們需要把上面的代碼改成下面這種:
public static void streamWithCheckedException(){
Stream.of(new MyStudents()).map(s-> {
try {
return s.changeAgeWithCheckedException();
} catch (MyCheckedException e) {
e.printStackTrace();
}
}).toList();
}
這樣做是不是就可以了呢?
再考慮一個(gè)情況,如果stream中不止一個(gè)map操作,而是多個(gè)map操作,每個(gè)map都拋出一個(gè)checkedException,那豈不是要這樣寫?
public static void streamWithCheckedException(){
Stream.of(new MyStudents()).map(s-> {
try {
return s.changeAgeWithCheckedException();
} catch (MyCheckedException e) {
e.printStackTrace();
}
}).map(s-> {
try {
return s.changeAgeWithCheckedException();
} catch (MyCheckedException e) {
e.printStackTrace();
}
}).
toList();
}
實(shí)在是太難看了,也不方便書寫,那么有沒有什么好的方法來處理,lambda中的checked異常呢?辦法當(dāng)然是有的。
lambda中的unchecked exception
上面例子中我們拋出了一個(gè)checked exception,那么就必須在lambda表達(dá)式中對(duì)異常進(jìn)行捕捉。
那么我們可不可以換個(gè)思路來考慮一下?
比如,把上面的checked exception,換成unchecked exception會(huì)怎么樣呢?
public static void streamWithUncheckedException(){
Stream.of(new MyStudents()).map(MyStudents::changeAgeWithUnCheckedException).toList();
}
我們可以看到程序可以正常編譯通過,可以減少或者幾乎不需要使用try和catch,這樣看起來,代碼是不是簡潔很多。
那么我們是不是可以考慮把checked exception轉(zhuǎn)換成為unchecked exception,然后用在lambda表達(dá)式中,這樣就可以簡化我們的代碼,給程序員以更好的代碼可讀性呢?
說干就干。
基本的思路就是把傳入的checked exception轉(zhuǎn)換為unchecked exception,那么怎么轉(zhuǎn)換比較合適呢?
這里我們可以用到JDK中的類型推斷,通過使用泛型來達(dá)到這樣的目的:
public static <T extends Exception,R> R sneakyThrow(Exception t) throws T {
throw (T) t;
}
這個(gè)方法接收一個(gè)checked exception,在內(nèi)部強(qiáng)制轉(zhuǎn)換之后,拋出T。
看看在代碼中如何使用:
public static void sneakyThrow(){
Stream.of(new MyStudents()).map(s -> SneakilyThrowException.sneakyThrow(new IOException())).toList();
}
代碼可以編譯通過,這說明我們已經(jīng)把checked異常轉(zhuǎn)換成為unchecked異常了。
運(yùn)行之后你可以得到下面的輸出:
Exception in thread "main" java.io.IOException
at com.flydean.Main.lambdasneakyThrow1(Main.java:28)
at java.base/java.util.stream.ReferencePipeline31.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.Streams$StreamBuilderImpl.forEachRemaining(Streams.java:411)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)
at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622)
at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627)
at com.flydean.Main.sneakyThrow(Main.java:28)
at com.flydean.Main.main(Main.java:9)
從日志中,我們可以看出最后拋出的還是java.io.IOException,但是如果我們嘗試對(duì)這個(gè)異常進(jìn)行捕獲:
public static void sneakyThrow(){
try {
Stream.of(new MyStudents()).map(s -> SneakilyThrowException.sneakyThrow(new IOException())).toList();
}catch (IOException e){
System.out.println("get exception");
}
}
在編譯器中會(huì)提示編譯不通過,因?yàn)榇a并不會(huì)拋出IOException。如果你把IOException修改為RuntimeException,也沒法捕獲到最后的異常。
只能這樣修改:
public static void sneakyThrow(){
try {
Stream.of(new MyStudents()).map(s -> SneakilyThrowException.sneakyThrow(new IOException())).toList();
}catch (Exception e){
System.out.println("get exception");
}
}
才能最終捕獲到stream中拋出的異常。所以如果你使用了我這里說的這種異常轉(zhuǎn)換技巧,那就必須要特別注意這種異常的捕獲情況。
對(duì)lambda的最終改造
上面可以封裝異常了是不是就完成了我們的工作了呢?
并不是,因?yàn)槲覀冊趍ap中傳入的是一個(gè)Function而不是一個(gè)專門的異常類。所以我們需要對(duì)Function進(jìn)行額外的處理。
首先JDK中的Function中必須實(shí)現(xiàn)這樣的方法:
R apply(T t);
如果這個(gè)方法里面拋出了checked Exception,那么必須進(jìn)行捕獲,如果不想捕獲的話,我們可以在方法申明中拋出異常,所以我們需要重新定義一個(gè)Function,如下所示:
@FunctionalInterface
public interface FunctionWithThrow<T, R> {
R apply(T t) throws Exception;
}
然后再定義一個(gè)unchecked方法,用來對(duì)FunctionWithThrow進(jìn)行封裝,通過捕獲拋出的異常,再次調(diào)用sneakyThrow進(jìn)行checked異常和unchecked異常的轉(zhuǎn)換:
static <T, R> Function<T, R> unchecked(FunctionWithThrow<T, R> f) {
return t -> {
try {
return f.apply(t);
} catch (Exception ex) {
return SneakilyThrowException.sneakyThrow(ex);
}
};
}
最后,我們就可以在代碼中優(yōu)雅的使用了:
public static void sneakyThrowFinal(){
try {
Stream.of(new MyStudents()).map(SneakilyThrowException.unchecked(MyStudents::changeAgeWithCheckedException)).toList();
}catch (Exception e){
System.out.println("get exception");
}
}
總結(jié)
以上就是如何在lambda表達(dá)式中優(yōu)雅的進(jìn)行異常轉(zhuǎn)換的例子了。大家使用的過程中一定要注意最后對(duì)異常的捕獲。
好了,本文的代碼:
本文的例子https://github.com/ddean2009/learn-java-base-9-to-20/tree/master/lambda-and-checked-exception/
作者:程序那些事
歡迎關(guān)注微信公眾號(hào) :程序那些事