簡體   English   中英

通用異常類型的Java類型推斷

[英]Java type inference of generic exception type

我正在嘗試使用一個可能拋出多個異常的仿函數F(在下面的示例中,Checked和SQLException)。 我希望能夠使用F作為參數調用一個函數,這樣任何檢查的異常F拋出(除了將在內部處理的SQLException)都會被重新拋出。

import java.sql.Connection;
import java.sql.SQLException;

class Checked extends Exception {
    public Checked() {
        super();
    }
}

@FunctionalInterface
interface SQLExceptionThrowingFunction<T, U, E extends Exception> {
    U apply(T t) throws E, SQLException;
}

class ConnectionPool {
    public static <T, E extends Exception> T call(Class<E> exceptionClass, SQLExceptionThrowingFunction<Connection, T, E> f) throws E {
        throw new UnsupportedOperationException("unimportant");
    }
}

class Test {
    static Void mayThrow0(Connection c) {
        throw new UnsupportedOperationException("unimportant");
    }        
    static <E extends Exception> Void mayThrow1(Connection c) throws E {
        throw new UnsupportedOperationException("unimportant");
    }
    static <E1 extends Exception, E2 extends Exception> Void mayThrow2(Connection c) throws E1, E2 {
        throw new UnsupportedOperationException("unimportant");
    }

    public static void main(String[] args) throws Exception {
        // Intended code, but doesn't compile
        ConnectionPool.call(RuntimeException.class, Test::<SQLException>mayThrow1);
        ConnectionPool.call(Checked.class, Test::<Checked, SQLException>mayThrow2);

        // Type inference works if the function doesn't actually throw SQLException (doesn't help me)
        ConnectionPool.call(RuntimeException.class, Test::mayThrow0);
        ConnectionPool.call(Checked.class, Test::<Checked>mayThrow1);

        // Can workaround by manually specifying the type parameters to ConnectionPool.call (but is tedious)
        ConnectionPool.<Void, RuntimeException>call(RuntimeException.class, Test::<SQLException>mayThrow1);
        ConnectionPool.<Void, Checked>call(Checked.class, Test::<Checked, SQLException>mayThrow2);
    }
}

直觀地說,我希望上面的例子能夠編譯,但事實並非如此。 有沒有辦法讓這個工作,或者是唯一的方法指定類型參數的解決方法? 編譯錯誤是:

Test.java:34: error: incompatible types: inference variable E has incompatible bounds
        ConnectionPool.call(RuntimeException.class, Test::<SQLException>mayThrow1); // doesn't compile
                           ^
    equality constraints: RuntimeException
    lower bounds: SQLException
  where E,T are type-variables:
    E extends Exception declared in method <T,E>call(Class<E>,SQLExceptionThrowingFunction<Connection,T,E>)
    T extends Object declared in method <T,E>call(Class<E>,SQLExceptionThrowingFunction<Connection,T,E>)
Test.java:35: error: incompatible types: inference variable E has incompatible bounds
        ConnectionPool.call(Checked.class, Test::<Checked, SQLException>mayThrow2); // doesn't compile
                           ^
    equality constraints: Checked
    lower bounds: SQLException,Checked
  where E,T are type-variables:
    E extends Exception declared in method <T,E>call(Class<E>,SQLExceptionThrowingFunction<Connection,T,E>)
    T extends Object declared in method <T,E>call(Class<E>,SQLExceptionThrowingFunction<Connection,T,E>)
2 errors

Java解析器有一個奇怪的特性(在jdk 1.8u152和9.0.1中,但不是 Eclipse內置的編譯器)所以當你有

@FunctionalInterface
interface SQLExceptionThrowingFunction<T, U, E extends Exception> {
    U apply(T t) throws E, SQLException;
}

然后傳遞Test::<SQLException>mayThrow1它在創建接口實例時將E綁定到SQLException。

你可以把它這樣做,只需在界面交換的聲明異常,即只是做

@FunctionalInterface
interface SQLExceptionThrowingFunction<T, U, E extends Exception> {
    U apply(T t) throws SQLException, E;
}

然后編譯!

JLS的相關部分是第18.2.5節 但我無法看到它解釋上述行為的位置。

對不起我的評論,它實際上沒有編譯,但它以某種方式在Eclipse上運行。 我認為編譯錯誤實際上是預期的。 調用方法的簽名是:

public static <T, E extends Exception> T call(Class<E> exceptionClass, SQLExceptionThrowingFunction<Connection, T, E> f) throws E

你正在使用它:

ConnectionPool.call(RuntimeException.class, Test::<SQLException>mayThrow1);

通過該方法的簽名,第一個參數(RuntimeException)的類必須與mayThrow1(SQLException)的泛型匹配,因為它們在簽名中都是E.

當你看到public static <T, E extends Exception> T call(Class<E> exceptionClass, SQLExceptionThrowingFunction<Connection, T, E> f) throws E告訴:

  • Class<E>E類型(您的第一個參數,exceptionClass)和
  • SQLExceptionThrowingFunction<Connection, T, E> f) throws EE類型SQLExceptionThrowingFunction<Connection, T, E> f) throws E

應具有相同的類型/子類型。

因此, E (即SQLException中) SQLExceptionThrowingFunction預計的亞型E (exceptionClass),其作為傳遞RuntimeException )。 (當你調用ConnectionPool.call(RuntimeException.class, Test::<SQLException>mayThrow1);時會發生這種情況ConnectionPool.call(RuntimeException.class, Test::<SQLException>mayThrow1);

由於此期望失敗,您將收到編譯錯誤。

您可以通過更改來驗證這一點

  • ConnectionPool.call(RuntimeException.class, Test::<SQLException>mayThrow1);
  • ConnectionPool.call( Exception .class, fitest.Test::<SQLException>mayThrow1); 這將刪除該行上的錯誤。

不確定這是否是您最初的意圖。

1:你可以做些什么來使用通用的東西(如果你不關心聲明異常是改變調用方法,如下所示,那么你的所有代碼都可以工作。

public static <T> T call2(Class exceptionClass, SQLExceptionThrowingFunction<Connection,T, Exception> f)
{
        throw new UnsupportedOperationException("unimportant");
}

2:或者你可以在沒有定義類型的情況下調用。 例如

ConnectionPool.call(RuntimeException.class, Test::mayThrow0);
ConnectionPool.call(Checked.class, Test::mayThrow1);

我不確定這是否能解決你的問題。 如果你有不同的意圖,當你說Is there a way to get this to work, or is the workaround of specifying the type arguments the only way實際上你想要Is there a way to get this to work, or is the workaround of specifying the type arguments the only way然后請分享pseduo語法你想如何工作的東西。

暫無
暫無

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

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