I'm trying to have a functor F which may throw multiple exceptions (in the example below Checked and SQLException). I want to be able to call a function with F as an argument, such that whatever checked exceptions F throws (except SQLException which would be handled internally) get rethrown.
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);
}
}
Intuitively, I would expect the above example to compile but it doesn't. Is there a way to get this to work, or is the workaround of specifying the type arguments the only way? The compile error is:
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
There is a strange peculiarity of the Java parser (in jdk 1.8u152 and 9.0.1, but not the compiler built into Eclipse) so when you have
@FunctionalInterface
interface SQLExceptionThrowingFunction<T, U, E extends Exception> {
U apply(T t) throws E, SQLException;
}
and you pass Test::<SQLException>mayThrow1
it binds E to SQLException when it creates an instance of the interface.
You can make it not do that by simply swapping the declared exceptions in the interface, i.e. just do
@FunctionalInterface
interface SQLExceptionThrowingFunction<T, U, E extends Exception> {
U apply(T t) throws SQLException, E;
}
and then it compiles!
The relevant part of the JLS is section 18.2.5. But I can't see where it explains the above behaviour.