這樣也行,在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) :程序那些事