我正在尝试使用一个可能抛出多个异常的仿函数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
答案 0 :(得分:6)
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的相关部分是section 18.2.5。但我无法看到它解释上述行为的位置。
答案 1 :(得分:1)
对不起我的评论,它实际上没有编译,但它以某种方式在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.
答案 2 :(得分:0)
当你看到public static <T, E extends Exception> T call(Class<E> exceptionClass, SQLExceptionThrowingFunction<Connection, T, E> f) throws E
告诉:
E
中Class<E>
的类型(您的第一个参数,exceptionClass)和E
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(
例外 .class, fitest.Test::<SQLException>mayThrow1);
将删除该行的错误。不确定这是否是您最初的意图。
1: 你可以做些什么来使用通用的东西(如果你不关心声明异常是改变调用方法,如下所示,那么你的所有代码都可以工作。
public static <T> T call2(Class exceptionClass, SQLExceptionThrowingFunction<Connection,T, Exception> f)
{
throw new UnsupportedOperationException("unimportant");
}
2:或者您可以在不定义类型的情况下进行调用。 e.g
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
实际上您想要什么时,请分享pseduo语法,您希望如何使用这些内容。