簡體   English   中英

如何在java中編寫未經檢查的Throwable

[英]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();
    }

}

請注意,在maindoSomething中都沒有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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM