通用异常类型的Java类型推断

Gre*_*ers 7 java generics

我正在尝试使用一个可能抛出多个异常的仿函数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);
    }
}
Run Code Online (Sandbox Code Playgroud)

直观地说,我希望上面的例子能够编译,但事实并非如此.有没有办法让这个工作,或者是唯一的方法指定类型参数的解决方法?编译错误是:

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
Run Code Online (Sandbox Code Playgroud)

Kli*_*cou 6

Java解析器有一个奇怪的特性(在jdk 1.8u152和9.0.1中,但不是 Eclipse内置的编译器)所以当你有

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

并且Test::<SQLException>mayThrow1当它创建接口的实例时,它传递它将E绑定到SQLException.

你可以把它不能做到这一点,只需在界面交换的声明异常,即只是做

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

然后编译!

JLS的相关部分是第18.2.5节.但我无法看到它解释上述行为的位置.