如何優雅地在 Java 8 中處理異常

前言

Java 8 引入的流 (Stream) API 和 lambda 表達式爲咱們打開了新世界的大門,自此以後咱們也能夠在 Java 中進行函數式編程了。然而,在實際工做中,許多小夥伴並不知道如何正確的在 lambda 中處理異常,今天就來給你們講解一下。面試

 

咱們都知道,Java 異常分爲檢查異常和非檢查異常。檢查異常就是編譯器要求開發者必須處理的異常,而非檢查異常則沒有這個要求。因此當咱們須要調用某個拋出檢查異常的方法時,必須明確捕獲它:spring

myList.stream()
  .map(item  ->
      try{
        return doSomething(item);
      } catch(MyException e){
        throw new RuntimeException (e);
      }
    })
    .forEach(System.out::printion);

如上面代碼所示,咱們捕獲了 MyException 這個檢查異常,而後將其轉化爲 RuntimeException 非檢查異常,從新拋出。可是你本身內心面其實清楚的很,這不是最好的處理方式。編程

優化一: 提高可讀性

以下所示,咱們將方法體單獨提取到 trySomething 方法中,這樣的話,咱們就可使用一行代碼完成 lambda 表達式,整個代碼可讀性也會提高很多:後端

myList.stream()
  .map(this::trySomething)
  .forEach(System.out::printion);

private Item trySomething(Item item) {
   try{
     return doSomething(item);
   } catch(MyException e){
     throw new RuntimeException (e);
   }
}

優化二: 複用代碼

如今你已經解決了上述的問題,然而當咱們再碰到須要處理異常的其它方法時,難道咱們都要用 try ... catch ... 包裹一層嗎?那樣的話,你能夠想象代碼中可能處處都是這種相似的寫法。爲了不陷入到這種重複的寫法中,咱們應該將上述代碼片斷抽象爲一個小的工具類,專門用來幹這件事情。你只須要定義一次,而後再須要的地方屢次調用它就能夠了。設計模式

爲了實現這個目標,咱們首先須要本身定義一個函數式接口,這個接口可能會拋出一個異常:性能優化

而後,咱們來寫一個靜態幫助函數 wrap ,該方法接受一個函數式接口參數,在方法體內捕獲檢查異常,並拋出非檢查異常 RuntimeException:mybatis

藉助於 wrap 靜態函數,如今你能夠在 lambda 表達式中這麼寫了併發

優化三: 出現異常時繼續運行

上述代碼的可讀性、抽象性已經很好了,然而還存在一個比較大的問題,那就是當出現異常的時候,你的 stream 代碼會當即中止,不會接着處理下一個元素。大多數狀況下,當拋出異常的時候,咱們可能還想讓 stream 繼續運行下去。app

咱們與其拋出異常,將異常當成一種特殊的狀況處理,還不如直接將異常當成是一個 「正常」 的返回值。即這個函數要麼返回一個正確的結果,要麼返回一個異常,因此咱們如今須要定義一個新的封裝類 Either,用來存儲這兩種結果。爲了方便,咱們將異常存儲到 left 這個字段中,將正常返回的值存儲到 right 這個字段中。下面就是 Either 類的一個簡單示例:分佈式

public class Eithercl<L,R>{

     private final L Left:
     private final R right;

     private Either(L left, R right){
         this left=left;
         this right =right;
}
public static <L, R> Either,<L,R> Left( L value) {
    return new Either(value, null):
}
public static <L, R> Either<L, R> Right( R value) {
    return new Either(null, value)
}
public Optional<L> getleft() {
    return Optional. ofnullable(left)
}
public Optional<R> getright() {
    return Optional.ofnullable(right);
}
public boolean isleft() {
    return left I- null;
}
public boolean isright(){
    return right != null;
}
public < T> optional<T> mapleft(Function<? super L, T> mapper){
    if (isleft()) {
        return Optional of(mapper. apply(left));
     }
     return Optional empty();
}
public <T> Optional<T> mapright(Function<? super R, T> mapper) {
    if (isright()) {
         return Optional of(mapper. apply(right));
    }
    return Optionalempty();
}
public String tostring(){
    if (isleft()){
        return"Left(」+left+")";
    }
    return "Right("+ right +")";
  } 
}

如今咱們須要再定義一個 lift 函數,該函數內部將 function 函數正常返回的值或者拋出的異常都使用 Either 類進行了一層封裝

如今咱們的代碼變成這個樣子了,也不用擔憂方法拋出異常會提早終止 Stream 了

優化四: 保留原始值

如今思考一個問題,若是在上述處理過程當中,當結果是異常信息的時候,咱們想要重試,即從新調用這個方法怎麼辦? 你會發現咱們 Either 封裝類沒有保存最原始的這個值,咱們丟掉了原始值,所以咱們能夠進一步優化,將原始值 t 也封裝進 left 字段中,就像下面這樣:

Pair 類是一個很是簡單的封裝類,用以封裝兩個值:

public class Pair<F, S> {
    public final F fst;
    public final S snd;

    private Pair(F fst, S snd){
         this fst fst;
         this snd= snd;
    }
public static <F, S> Pair<F, S> of(F fst, S snd){
    return new Pair<>(fst, snd);
    }
}

這樣,當咱們碰見異常的時候,咱們能夠從 Pair 中取出最原始的值 t,不管是想重試,仍是作一些其餘操做,都很方便了。

小編給你們推薦一個Java後端技術羣:479499375!羣內提供設計模式、spring/mybatis源碼分析、高併發與分佈式、微服務、性能優化,面試題整合文檔等免費資料!給你們提供一個交流學習的平臺!

總結

咱們通過上文一點一點地優化代碼,獲得了一個比較滿意的在 Java 8 中處理異常的通用方式。其實,你們還能夠關注 Github 上的有關函數式編程方面的庫,好比 Javaslang ,它實現了多種多樣的函數式幫助方法和封裝類來幫助開發者寫好 lambda 表達式。可是,若是你只是爲了處理異常,而引入這麼大的一個第三方庫的話,就不太建議了哦~

相關文章
相關標籤/搜索