Lombok之@SneakyThrows
前言
@SneakyThrows註解的用途得從java的異常設計體系說起
Throwable是Error和Exception的父類,用來定義所有可以作為異常被丟擲來的類。
Error和Exception區分:
Error是編譯時錯誤和系統錯誤,系統錯誤在除特殊情況下,都不需要你來關心,基本不會出現。而編譯時錯誤,如果你使用了編譯器,那麼編譯器會提示。
Exception則是可以被丟擲的基本型別,我們需要主要關心的也是這個類。
Exception又分為RunTimeException和其他Exception。
RunTimeException和其他Exception區分:
其他Exception,受檢查異常。可以理解為錯誤,必須要開發者解決以後才能編譯通過,解決的方法有兩種,
- throw到上層,
- try-catch處理。
RunTimeException:執行時異常,又稱不受檢查異常,不受檢查,因為不受檢查,所以在程式碼中可能會有RunTimeException時Java編譯檢查時不會告訴你有這個異常,但是在實際執行程式碼時則會暴露出來,比如經典的1/0,空指標等。如果不處理也會被Java自己處理。
Lombok之@SneakyThrows
以往異常捕獲
何為SneakyThrows?從字面理解就是“偷偷摸摸的丟擲”。對的,就是這個意思。
該註解屬於Lombok,它的作用為減少程式的異常捕獲。
我們現在寫程式碼,如果遇到異常,通常需要try catch,或者直接throws拋給上一層。
例如:
1 public static void main(String[] args) { 2 Class clz = null; 3 try { 4 clz = Class.forName("com.woshild.derek_ld.lombok.SneakyThrowsTest"); 5 System.out.println(clz.getName()); 6 Thread.sleep(3000); 7 System.out.println("3秒已過。。。");8 } catch (ClassNotFoundException e) { 9 e.printStackTrace(); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 }
1 public static void main(String[] args) throws ClassNotFoundException, InterruptedException { 2 Class clz = Class.forName("com.woshild.derek_ld.lombok.SneakyThrowsTest"); 3 System.out.println(clz.getName()); 4 5 Thread.sleep(3000); 6 System.out.println("3秒已過。。。"); 7 }
以上兩種情況都是日常幾乎每天都要寫的。比如Thread.sleep的異常,每次都需要捕獲或丟擲。很是煩躁。
再或者有時程式裡有異常處理較多的時候,直接catch Exception e ,簡單直接粗暴的捕獲異常。
但如此這般,依然有些麻煩。
初見@SneakyThrows
今天突然發現這個註解@SneakyThrows,先看看他是如何用的。
1 @SneakyThrows 2 public static void main(String[] args) { 3 Class clz = Class.forName("com.woshild.derek_ld.lombok.SneakyThrowsTest"); 4 System.out.println(clz.getName()); 5 6 Thread.sleep(3000); 7 System.out.println("3秒已過。。。"); 8 }
看到沒有,直接在方法上面加上@SneakyThrows即可,再也不需要處理異常了,是不是很香?
再來看看生成的class檔案長什麼樣。
1 public static void main(String[] args) { 2 try { 3 Class clz = Class.forName("com.woshild.derek_ld.lombok.SneakyThrowsTest"); 4 System.out.println(clz.getName()); 5 Thread.sleep(3000L); 6 System.out.println("3秒已過。。。"); 7 } catch (Throwable var2) { 8 throw var2; 9 } 10 }
看,@SneakyThrows直接把捕獲異常的程式碼嵌入到了class檔案裡。
也就是說,並不是我們忽略了異常,而是類似於Lombok的@Data註解,在編譯時就已經把處理的程式碼嵌入到了class內。
當然,我們也可以自定義需要@SneakyThrows處理的異常,比如例子中,我只需要他幫我處理Thread.sleep的異常,關於Class.forName的異常我需要額外做處理。可以這麼做:
1 @SneakyThrows(InterruptedException.class) 2 public static void main(String[] args) { 3 Class clz = null; 4 try { 5 clz = Class.forName("com.woshild.derek_ld.lombok.SneakyThrowsTest"); 6 System.out.println(clz.getName()); 7 } catch (ClassNotFoundException e) { 8 //todo: ... 9 log.error("[com.woshild.derek_ld.lombok.SneakyThrowsTest] class not found.", e); 10 } 11 12 Thread.sleep(3000); 13 System.out.println("3秒已過。。。"); 14 }
在看一下@SneakyThrows註解的定義:
1 @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) 2 @Retention(RetentionPolicy.SOURCE) 3 public @interface SneakyThrows { 4 Class<? extends Throwable>[] value() default {Throwable.class}; 5 }
-
它可用於方法和構造器。
-
value是Class<? extends Throwable>[],是Throwable異常的子類陣列。
原理
摘自網文
顯然魔法 藏在
Lombok.sneakyThrow(t);
中。可能大家都會以為這個方法就是new RuntimeException()之類的。然而事實並非如此。閱讀程式碼可以看出整個方法其實最核心的邏輯是throw (T)t;
,利用泛型將我們傳入的Throwable強轉為RuntimeException。雖然事實上我們不是RuntimeException。但是沒關係。因為JVM並不關心這個。泛型最後儲存為位元組碼時並沒有泛型的資訊。這樣寫只是為了騙過javac編譯器。原始碼中註釋有解釋。
1 public static RuntimeException sneakyThrow(Throwable t) { 2 if (t == null) throw new NullPointerException("t"); 3 return Lombok.<RuntimeException>sneakyThrow0(t); 4 } 5 6 private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T { 7 throw (T)t; 8 }
總結
- 此註解雖神奇,但慎用。因為一旦用了,如果你的程式碼遇到了異常,那麼在編碼階段你是無法感知的。
- 如果你的程式碼裡沒有異常,然而卻聲明瞭該註解,反而會引起不必要的誤會。