簡體   English   中英

為什么Java 8類型推斷不考慮Lambdas在重載選擇中引發的異常?

[英]Why Doesn't Java 8 Type Inference Consider Exceptions Thrown by Lambdas in Overload Selection?

關於lambdas及其相關的異常簽名,我有一個關於Java 8推斷的問題。

如果我定義一些方法foo:

public static <T> void foo(Supplier<T> supplier) {
    //some logic
    ...
}

然后我得到了能夠編寫foo(() -> getTheT());簡潔語義foo(() -> getTheT()); 在大多數情況下,對於給定的T 但是,在此示例中,如果我的getTheT操作聲明它throws Exception ,則我的foo方法會使Supplier不再編譯: get的Supplier方法簽名不會拋出異常。

這似乎是解決這個問題的一個不錯的方法是重載foo以接受任一選項,重載定義為:

public static <T> void foo(ThrowingSupplier<T> supplier) {
   //same logic as other one
   ...
}

其中ThrowingSupplier定義為

public interface ThrowingSupplier<T> {
   public T get() throws Exception;
}

通過這種方式,我們有一個引發異常的供應商類型和一個不引發異常的供應商類型。 所需的語法將是這樣的:

foo(() -> operationWhichDoesntThrow()); //Doesn't throw, handled by Supplier
foo(() -> operationWhichThrows()); //Does throw, handled by ThrowingSupplier

但是,這會導致問題,因為lambda類型不明確(可能無法在Supplier和ThrowingSupplier之間解決)。 做一個顯式的演員表foo((ThrowingSupplier)(() -> operationWhichThrows())); 會工作,但它擺脫了所需語法的大部分簡潔性。

我想基本的問題是:如果Java編譯器能夠解決我的一個lambdas由於它在僅供應商案例中拋出異常而不兼容的事實,為什么它不能使用相同的信息來導出二級,類型推理案例中lambda的類型?

任何人都可以指出的任何信息或資源同樣值得贊賞,因為我不太確定在哪里可以找到有關此事的更多信息。

謝謝!

如果它讓您感覺更好,那么在JSR-335設計過程中確實會仔細考慮這個主題。

問題不是“為什么不能”,而是“為什么我們選擇不這樣做”。 當我們發現多個可能適用的重載時,我們當然可以選擇在每組簽名下推測性地歸屬lambda主體,並修剪lambda主體未能進行類型檢查的候選者。

但是,我們的結論是,這樣做可能弊大於利; 例如,這意味着,根據此規則,對方法主體的微小更改可能會導致某些方法過載選擇決策在沒有用戶打算這樣做的情況下進行靜默更改。 最后,我們得出結論,使用方法體中存在的錯誤來丟棄可能適用的候選者會導致更多的混淆而不是利益,特別是考慮到有一個簡單而安全的解決方法 - 提供目標類型。 我們認為這里的可靠性和可預測性超過了最佳的簡潔性。

首先,你不必過載:D - 重載永遠不是必需的; 使用2個不同的方法名稱,例如foofooX

其次,我不明白你為什么需要2種方法。 如果要以不同方式處理已檢查和未檢查的異常,可以在運行時完成。 要實現“異常透明度”,您可以這樣做

interface SupplierX<T, X extends Throwable>
{
    T get() throws X;
}

<T, X extends Throwable> void foo(Supplier<T, X> supplier)  throws X { .. }


foo( ()->"" );  // throws RuntimeException

foo( ()->{ throw new IOException(); } );  // X=IOException

最后,可以實現拋棄lambda返回類型的歧義; 編譯器使用返回類型,就像使用參數類型選擇最具體的方法一樣。 這使我們有了將值與異常類型一起包裝的Result<T,X> ,如Result<T,X> ,就像他們所說的“monad”。

interface Result<T, X extends Throwable>
{
    T get() throws X;
}

// error type embedded in return type, not in `throws` clause

static Result<String,        Exception> m1(){ return ()->{ throw new Exception();};  }
static Result<String, RuntimeException> m2(){ return ()->{ return "str";         };  }

  // better to have some factory method, e.g. return Result.success("str");

public static void main(String[] args)
{
    foo(()->m1());  // foo#2 is not applicable
    foo(()->m2());  // both applicable; foo#2 is more specific
}



interface S1<T> { T get(); }  

static <T> void foo(S1<Result<T, ? extends        Exception>> s)
{
    System.out.println("s1");}
}


interface S2<T> { T get(); }  // can't have two foo(S1) due to erasure

static <T> void foo(S2<Result<T, ? extends RuntimeException>> s)
{
    System.out.println("s2");
}

任何可以被接受為Supplier<T> lambda也可以被接受為ThrowingSupplier<T> 以下編譯:

public static interface ThrowingSupplier<T>{
    public T get() throws Exception;
}

public static <T> void foo(ThrowingSupplier<T> supplier) {

}

public static String getAString(){
    return "Hello";
}

public static String getAnotherString() throws Exception{
    return "World";
}

public static void main(String[] args) {
    foo(()->getAString());
    foo(()->getAnotherString());
} 

鑒於上述情況,您可能不需要這樣,但如果foo 必須接受非拋出的Supplier<T> ,您始終可以將異常拋出方法包裝在一個方法中,該方法將其清除為未經檢查的異常:

public static <T> void foo(Supplier<T> supplier) {

}

public static String getAString(){
    return "Hello";
}

public static String getAnotherString() throws Exception{
    return "World";
}

public static String getAnotherStringUnchecked(){
    try{
        return getAnotherString();
    } catch(Exception e){
        throw new RuntimeException("Error getting another string",e);
    }
}   

public static void main(String[] args) throws Exception{
    foo(()->getAString());
    foo(()->getAnotherStringUnchecked());
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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