這樣也行,在lambda表達式中優雅的處理checked exception

簡介
最近發現很多小伙伴還不知道如何在lambda表達式中優雅的處理checked exception,所以今天就重點和大家來探討一下這個問題。

lambda表達式本身是為了方便程序員書寫方便的工具,使用lambda表達式可以讓我們的代碼更加簡潔。

可能大多數小伙伴在使用的過程中從來沒有遇到過里面包含異常的情況,所以對這種在lambda表達式中異常的處理可能沒什么經驗。

不過沒關系,今天我們就來一起探討一下。

lambda表達式中的checked exception
java中異常的類型,大家應該是耳熟能詳了,具體而言可以有兩類,一種是checked exception, 一種是unchecked exception。

所謂checked exception就是需要在代碼中手動捕獲的異常。unchecked exception就是不需要手動捕獲的異常,比如運行時異常。

首先我們定義一個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);
    }
}

接下來我們定義一個類,這個類中有兩個方法,一個拋出checked exception,一個拋出unchecked exception:

public class MyStudents {

    public int changeAgeWithCheckedException() throws MyCheckedException {
        throw new MyCheckedException();
    }

    public int changeAgeWithUnCheckedException(){
        throw new RuntimeException();
    }
}
好了,我們首先在lambda表達式中拋出CheckedException:

    public static void streamWithCheckedException(){
        Stream.of(new MyStudents()).map(s->s.changeAgeWithCheckedException()).toList();
    }
這樣寫在現代化的IDE中是編譯不過的,它會提示你需要顯示catch住CheckedException,所以我們需要把上面的代碼改成下面這種:

    public static void streamWithCheckedException(){
        Stream.of(new MyStudents()).map(s-> {
            try {
                return s.changeAgeWithCheckedException();
            } catch (MyCheckedException e) {
                e.printStackTrace();
            }
        }).toList();
    }
這樣做是不是就可以了呢?

再考慮一個情況,如果stream中不止一個map操作,而是多個map操作,每個map都拋出一個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();
    }
實在是太難看了,也不方便書寫,那么有沒有什么好的方法來處理,lambda中的checked異常呢?辦法當然是有的。






lambda中的unchecked exception
上面例子中我們拋出了一個checked exception,那么就必須在lambda表達式中對異常進行捕捉。

那么我們可不可以換個思路來考慮一下?

比如,把上面的checked exception,換成unchecked exception會怎么樣呢?

    public static void streamWithUncheckedException(){
        Stream.of(new MyStudents()).map(MyStudents::changeAgeWithUnCheckedException).toList();
    }
我們可以看到程序可以正常編譯通過,可以減少或者幾乎不需要使用try和catch,這樣看起來,代碼是不是簡潔很多。

那么我們是不是可以考慮把checked exception轉換成為unchecked exception,然后用在lambda表達式中,這樣就可以簡化我們的代碼,給程序員以更好的代碼可讀性呢?

說干就干。

基本的思路就是把傳入的checked exception轉換為unchecked exception,那么怎么轉換比較合適呢?

這里我們可以用到JDK中的類型推斷,通過使用泛型來達到這樣的目的:

    public static <T extends Exception,R> R sneakyThrow(Exception t) throws T {
        throw (T) t;
    }
這個方法接收一個checked exception,在內部強制轉換之后,拋出T。

看看在代碼中如何使用:

    public static void sneakyThrow(){
            Stream.of(new MyStudents()).map(s -> SneakilyThrowException.sneakyThrow(new IOException())).toList();
    }
代碼可以編譯通過,這說明我們已經把checked異常轉換成為unchecked異常了。

運行之后你可以得到下面的輸出:

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,但是如果我們嘗試對這個異常進行捕獲:

    public static void sneakyThrow(){
        try {
            Stream.of(new MyStudents()).map(s -> SneakilyThrowException.sneakyThrow(new IOException())).toList();
        }catch (IOException e){
           System.out.println("get exception");
        }
    }
在編譯器中會提示編譯不通過,因為代碼并不會拋出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中拋出的異常。所以如果你使用了我這里說的這種異常轉換技巧,那就必須要特別注意這種異常的捕獲情況。

對lambda的最終改造
上面可以封裝異常了是不是就完成了我們的工作了呢?

并不是,因為我們在map中傳入的是一個Function而不是一個專門的異常類。所以我們需要對Function進行額外的處理。

首先JDK中的Function中必須實現這樣的方法:

    R apply(T t);
如果這個方法里面拋出了checked Exception,那么必須進行捕獲,如果不想捕獲的話,我們可以在方法申明中拋出異常,所以我們需要重新定義一個Function,如下所示:

@FunctionalInterface
public interface FunctionWithThrow<T, R> {
    R apply(T t) throws Exception;
}

然后再定義一個unchecked方法,用來對FunctionWithThrow進行封裝,通過捕獲拋出的異常,再次調用sneakyThrow進行checked異常和unchecked異常的轉換:

    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);
            }
        };
    }
最后,我們就可以在代碼中優雅的使用了:

    public static void sneakyThrowFinal(){
        try {
            Stream.of(new MyStudents()).map(SneakilyThrowException.unchecked(MyStudents::changeAgeWithCheckedException)).toList();
        }catch (Exception e){
            System.out.println("get exception");
        }
    }
總結
以上就是如何在lambda表達式中優雅的進行異常轉換的例子了。大家使用的過程中一定要注意最后對異常的捕獲。

好了,本文的代碼:

本文的例子https://github.com/ddean2009/learn-java-base-9-to-20/tree/master/lambda-and-checked-exception/



作者:程序那些事


歡迎關注微信公眾號 :程序那些事