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

flydean發表於2023-04-12

簡介

最近發現很多小夥伴還不知道如何在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.lambda$sneakyThrow$1(Main.java:28)
    at java.base/java.util.stream.ReferencePipeline$3$1.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/

更多文章請看 www.flydean.com

相關文章