2015-06-10 122 views
22

讀取Optional的JavaDoc,我碰到了一個奇怪的方法簽名;我從來沒有在我的生活中看到:拋出x擴展異常方法簽名

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) 
           throws X extends Throwable 

乍一看,我想知道一般異常<X extends Throwable>如何,甚至有可能,因爲你不能做到這一點(herehere)。第二個想法是,這開始有意義,因爲它只是爲了綁定Supplier ...但供應商本身知道它應該是什麼類型,在仿製藥之前。

但第二線打我:

  • throws X是一個完整的通用的異常類型。

然後:

  • X extends Throwable,什麼世界呢是什麼意思?
    • X已經綁定在方法簽名中。
  • 請問這是以任何方式解決一般的例外限制嗎?
  • 爲什麼不只是throws Throwable,因爲其餘的將被類型擦除擦除?

和一個,不直接相關的問題:

  • 將需此方法被捕獲作爲catch(Throwable t),或者通過所提供的Supplier類型;因爲它不能在運行時檢查?
+4

不能有通用的'Exception'類型,但可以將'Exception'類型綁定到泛型參數。 –

+0

另外,這不是源代碼聲明,它是javadoc中的一些混淆(邊界)。 –

+0

@SotiriosDelimanolis我已經指出了。 – Mordechai

回答

16

對待它就像你讀過的任何其他通用代碼。

這裏的正式簽署從我在Java 8's source code看到:

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X 
  • X有一個上限的Throwable。這在以後很重要。
  • 我們返回一個類型T這勢必OptionalT
  • 我們預計Supplier其中有上限的X
  • 我們扔X(這是有效的通配符,因爲X有一個上限的Throwable )。這在JLS 8.4.6;只要X被視爲Throwable的子類型,其聲明在此處是合法且合法的。

There is an open bug關於Javadoc的誤導。在這種情況下,最好相信源代碼而不是文檔,直到bug被聲明爲固定。

至於爲什麼我們使用throws X代替throws ThrowableX保證在最上面的邊界綁定到Throwable。如果你想要一個更具體的Throwable(運行時間,選中或Error),那麼僅僅投擲Throwable不會給你這種靈活性。

要你最後一個問題:

將需要使用此方法在catch(的Throwable t)的條款被抓?

東西向下鏈具有處理異常,是一個try...catch塊或JVM本身。理想情況下,人們希望創建一個能夠最好地表達他們需求的異常的Supplier。你不必(並且可能應該而不是)爲這種情況創建一個catch(Throwable t);如果您的Supplier類型綁定到您需要處理的特定異常,那麼最好將其用作鏈接後面的catch

+0

我在我的最後一顆子彈中犯了一個小錯字,請檢查它。 – Mordechai

+0

此後又增加了另一個問題。 – Mordechai

+2

請謹慎**關於在問題中添加多個問題。我已經解決了先前的擔憂,我很樂意回顧修訂後的項目符號,但第二個問題是過度的。稍後我會添加該部分。 – Makoto

10

Javadoc的方法簽名是不同於源代碼。按照b132 source

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) 
       throws X { // Added by me: See? No X extends Throwable 
    if (value != null) { 
     return value; 
    } else { 
     throw exceptionSupplier.get(); 
    } 
} 

證實:這與Javadoc生成的問題。作爲測試,我創建了下面的類新項目和生成的Javadoc:

package com.stackoverflow.test; 

public class TestJavadoc { 
    public static <X extends Throwable> void doSomething() throws X { 

    } 
} 

這是造成的Javadoc:

Javadoc Screenshot

雙人證實:有一個open bug about the Javadoc for this class

3

泛型類型和泛型變量/參數之間有區別。

泛型類型是聲明泛型參數的類型。例如

class Generic<T> {} 

什麼是不允許的是一個通用類型(如上),也延伸Throwable

class Generic <T> extends Throwable {} // nono 

這是在Java語言規範表達,here

這是一個編譯時錯誤,如果一個泛型類是直接或間接的 子類Throwable(§11.1.1)。

但是,通用變量本身可以具有任何綁定,泛型或其他方式,只要它是引用類型即可。

X extends Throwable

意味着任何類型的結合(未具有邊界)在編譯時X必須是Throwable的子類型。

X已經綁定在方法簽名中。

X未綁定。該方法聲明其邊界。

請問這是以任何方式解決一般異常限制嗎?

爲什麼不extends Throwable,因爲其餘的將通過類型擦除被刪除?

泛型是一個編譯時功能。編譯後發生擦除。我相信他們可以簡單地使用

Supplier<? extends Throwable> 

作爲參數類型。但是你會失去在throws子句中使用的類型變量。

進行後期編輯:

爲什麼不throws Throwable,因爲其餘的將通過類型擦除被刪除?

您希望throws使用相同Exception類型的Supplier用品。

是否需要將此方法應用於catch(Throwable t)子句中?

否/取決於。如果您將檢查的異常綁定到X,那麼您需要以某種方式處理它。否則,不需要catch。但是最終會有/應該處理它,即使它是JVM本身。

+0

我在最後一個項目符號中犯了一個小錯字,請檢查它。 – Mordechai

+0

又增加了另一個問題,此後 – Mordechai

+1

@MouseEvent更新。 –

8

的Java不能趕上通用的異常類型,由於擦除

catch(FooException<Bar> e)  --analogy->  o instanceof List<String> 

catchinstanceof依賴於運行時類型信息;擦除廢墟。

但是,禁止像FooException<T>這樣的異常類型的泛型聲明有點過於苛刻。Java可以允許它;只是要求只有原始類型和通配符類型被允許在catch

catch(FooException e)    --analogy->  o instanceof List 
    catch(FooException<?> e)        o instanceof List<?> 

的參數可以作出,異常的類型主要是由catch子句興趣;如果我們無法捕捉到泛型異常類型,那麼首先將它們排除在外是沒有意義的。好。現在


,我們不能做到這一點無論

catch(X e) // X is a type variable 

那麼爲什麼Java的允許X在首位拋出?

... foo() throws X 

正如我們後面會看到的,這個構造在擦除的輔助下實際上可以顛覆類型系統。

嗯,這個功能雖然 - 至少在概念上是非常有用的。我們編寫泛型方法,以便我們可以爲未知輸入和返回類型編寫模板代碼;相同的投擲類型邏輯!例如,

<X extends Exception> void logAndThrow(X ex) throws X 
     ... 
     throw ex; 

    ... 
    (IOException e){ 
     logAndThrow(e); // throws IOException 

我們需要能夠一般性地在抽象代碼塊時實現異常透明。另外一個例子,假設我們已經厭倦了寫這種樣板反覆

lock.lock(); 
    try{ 
     CODE 
    }finally{ 
     lock.unlock(); 
    } 

的,我們要爲一個包裝方法,以在拉姆達的代碼

Util.withLock(lock,()->{ CODE }); 

的問題是,代碼可能拋出任何異常類型;原始的樣板拋出任何CODE拋出;並且我們希望writeLock()表達式可以執行相同的操作,即異常透明度。解決方案 -

interface Code<X extends Throwable> 
    { 
     void run() throws X; 
    } 



    <X extends Throwable> void withLock(Lock lock, Code<X> code) throws X 
     ... 
     code.run(); // throws X 

    ... 
    withLock(lock,()->{ throws new FooException(); }); // throws FooException 

這隻對檢查的異常很重要。無論如何,未經檢查的異常都可以自由傳播。

throws X方法的一個嚴重缺陷是它不適用於2個或更多的異常類型。


好吧,那真的很好。但是我們在新的JDK API中沒有看到這樣的用法。沒有任何方法參數的功能類型可以拋出任何檢查的異常。如果你這樣做Stream.map(x->{ BODY })BODY不能拋出任何檢查的異常。他們真的很討厭用lambdas檢查異常。

另一個例子是CompletableFuture,其中異常是整個概念的中心部分;但是,在lambda體中不允許檢查異常。

如果你是一個夢想家,你可能會相信在未來的某個版本的Java中,將會發明一種優雅的lambda異常透明機制,沒有像這樣醜陋的throws X黑客;因此我們現在不需要用<X> s污染我們的API。是的,夢想,男人。


那麼,throws X用了多少呢?

在整個JDK源代碼中,唯一公開的API是Optional.orElseThrow()(及其堂兄OptionalInt/Long/Double)。而已。

(有與orElseGet/orElseThrow設計一個缺點 - 分支的決定不能在lambda體內完畢後,我們可以改爲設計一個更常用的方法

T orElse(lambda) throws X 

    optional.orElse(()->{ return x; }); 
    optional.orElse(()->{ throw ex; }); 
    optional.orElse(()->{ if(..)return x; else throw ex; }); 

此外orElseThrow,在JDK中唯一的其他方法throws X是非公ForkJoinTask.uncheckedThrow()它用於「偷偷摸摸扔」,即代碼可以拋出檢查異常,而編譯器和運行不知情的情況 -

void foo() // no checked exception declared in throws 
    { 
     throw sneakyThrow(new IOExcepiton()); 
    } 

這裏,IOException在運行時從foo()丟棄;但編譯器和運行時未能阻止它。擦除主要是責備。

public static RuntimeException sneakyThrow(Throwable t) 
{ 
    throw Util.<RuntimeException>sneakyThrow0(t); 
} 
@SuppressWarnings("unchecked") 
private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T 
{ 
    throw (T)t; 
} 
+0

您的回答非常實用,謝謝。 – Mordechai