[英]How to write an unchecked Throwable in java
我想編寫一個未選中的自定義 Throwable。
有一些方法可以在拋出時欺騙編譯器(例如,將可拋出的對象重新拋出為未選中的實用程序類? ),我已經實現了:
public class CustomThrowable extends Throwable {
public CustomThrowable(String message) {
super(message);
}
@SuppressWarnings("unchecked")
public <T extends Throwable> T unchecked() throws T {
throw (T) this;
}
}
但我在“趕上時間”遇到了問題:
try {
throw new CustomThrowable("foo").unchecked();
}
catch (CustomThrowable t) { } // <- compiler error because the try block does not "throw" a CustomThrowable
有沒有辦法簡單地實現未經檢查的Throwable
,或者RuntimeException
是唯一的方法嗎? 我曾想避免從RuntimeException
繼承,因為我的 throwable 不是異常,而是 yield 指令。
更新:避免擴展RuntimeException
的另一個原因是我的CustomThrowable
將被通用catch (Exception ex) { }
塊捕獲。 因此,如果我想從堆棧中傳遞信息,每一層都需要潛在地意識到CustomThrowable
可能會通過並明確地捕獲重新拋出它; 這種意識是人們在使用Throwable
設計時試圖避免的很大一部分。
Throwable
是一個檢查過的錯誤,實際上應該不可能拋出此類錯誤,除非它是 RuntimeException 或 Error 的情況。
但實際上這是可能的。 這是一個例子。
這是一個實用程序,可將任何未經檢查的異常視為已檢查:
package org.mentallurg;
public class ExceptionUtil {
private static class ThrowableWrapper extends Throwable {
private Throwable throwable;
public ThrowableWrapper(Throwable throwable) {
super();
this.throwable = throwable;
}
@SuppressWarnings("unchecked")
public <T extends Throwable> T throwNested() throws T {
throw (T) throwable;
}
}
private static <T extends Throwable> T throwThis(T throwable) throws T {
throw throwable;
}
public static <T extends Throwable> void throwUnchecked(T throwable) {
new ThrowableWrapper(throwable).throwNested();
}
}
這是一個使用示例:
package org.mentallurg;
public class Test {
private static void doSomething() {
Exception checkedException = new Exception("I am checked exception");
ExceptionUtil.throwUnchecked(checkedException);
}
public static void main(String[] args) {
doSomething();
}
}
請注意,在main
和doSomething
中都沒有throws Throwable
子句。 並且沒有編譯錯誤。 在RuntimeException
的情況下是可以理解的,但在Throwable
的情況下則不然。
如果我們執行它,我們會得到以下信息:
Exception in thread "main" java.lang.Exception: I am checked exception
at org.mentallurg.Test.doSomething(Test.java:6)
at org.mentallurg.Test.main(Test.java:11)
這個怎么運作?
最重要的部分是這個:
new ThrowableWrapper(throwable).throwNested();
實際上,這里的throwNested
方法可以拋出一個Throwable
。 這就是為什么 Java 編譯器應該引發錯誤並且應該要求這行代碼要么被 try/catch 包圍,要么應該添加Throwable
子句。 但事實並非如此。 為什么? 我相信這是Java編譯器的一個缺陷。 其他線程中關於 SO 的一些評論提到了類型擦除,但它們是不正確的,因為類型擦除在運行時是相關的,而我們正在談論編譯時。
有趣的是,反編譯的代碼顯示這里將拋出Throwable
(不是RuntimeException
,不是Error
):
public static <T extends java.lang.Throwable> void throwUnchecked(T);
Code:
0: new #28 // class org/mentallurg/ExceptionUtil$ThrowableWrapper
3: dup
4: aload_0
5: invokespecial #30 // Method org/mentallurg/ExceptionUtil$ThrowableWrapper."<init>":(Ljava/lang/Throwable;)V
8: invokevirtual #32 // Method org/mentallurg/ExceptionUtil$ThrowableWrapper.throwNested:()Ljava/lang/Throwable;
11: pop
12: return
此行為並非特定於Throwable
。 我們可以用一個檢查的Exception
替換它,並且會得到相同的結果:
public <T extends Exception> T throwNested() throws T {
在所有這些情況下,如果我們用特定的類替換泛型,那么 Java 編譯器將報告錯誤,這是正確的。 在泛型的情況下,Java 編譯器會忽略檢查的異常並且不報告任何錯誤。 這就是為什么我認為這是Java 編譯器中的一個錯誤。
您可以擴展Error
而不是Throwable
。 Java Error
類是未經檢查的Throwable
。
Per 為什么運行時異常是未經檢查的異常? (以及 Jon Skeet 無可置疑的權威;),似乎 Java 編譯器內置了未經檢查的異常支持,並且可能不是我可以插入的東西。
引用喬恩的帖子:
它在規范中明確,第 11.1.1 節:
RuntimeException 及其所有子類統稱為運行時異常類。
未經檢查的異常類是運行時異常類和錯誤類。
已檢查異常類是除未檢查異常類之外的所有異常類。 也就是說,檢查的異常類都是 Throwable 的子類,除了 RuntimeException 及其子類和 Error 及其子類。
所以是的,編譯器肯定知道 RuntimeException。
我仍然希望其他人會提出“是的,你可以”的答案,所以我會再等幾天才能結束這個問題。
這種方法與mentallurg 的方法基本相同,但它避免了不必要的包裝類。
public final class ThrowUtil {
@SuppressWarnings("unchecked")
private static <T extends Throwable> void throwUnchecked0(Throwable t) throws T {
// because `T` is a generic it will be erased at compile time
// the cast will be replaced with `(Throwable) t` which will always succeed
throw (T) t;
}
public static void throwUnchecked(Throwable t) {
throwUnchecked0(t);
}
private ThrowUtil() {}
}
在您的代碼中:
// no throws clause
public void foo() {
ThrowUtil.throwUnchecked(new IllegalAccessException("you are not allowed to call foo"));
}
為了完整起見,這里是生成的字節碼進行比較:
private static <T extends java.lang.Throwable> void throwUnchecked0(java.lang.Throwable) throws T;
Code:
0: aload_0
1: athrow
public static void throwUnchecked(java.lang.Throwable);
Code:
0: aload_0
1: invokestatic #1 // Method throwUnchecked0:(Ljava/lang/Throwable;)V
4: return
如果throwUnchecked0
采用T
類型的參數而不是任何Throwable
參數,則 throws 子句或 try/catch 語句的要求將傳播到調用者1 。
有趣的是,在大多數情況下,這與泛型的行為非常不同。 例如,如果更改代碼以使內部方法返回T
而不是拋出T
,然后公共方法拋出該返回值,則T
的類型被推斷為Throwable
並且將傳播 throws 子句要求。 因此,這似乎確實是 Java 編譯器中的一個錯誤,盡管我希望看到它仍然存在,因為它仍然可以直接在字節碼中使用(並且其他 JVM 語言如 Kotlin 廣泛使用它)並且在某些情況下可能有用案例。
未經檢查的對T
的強制轉換是完全2安全的,因為T
在編譯時被擦除並替換為Throwable
,這將始終成功,因為參數是Throwable
。
對公共 api 隱藏真正的實現並不是絕對必要的。 它只是為了向讀者明確(並強制執行)泛型類型旨在被調用站點完全忽略和省略。
1調用者還可以顯式指定泛型參數的類型,並導致傳播 throws 子句的要求。 這是公共包裝方法的部分原因。
2好吧,只是在它在運行時永遠不會失敗的意義上。 畢竟,我們正在拋出未經檢查的異常。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.