Java type inference of general exception type

I am trying to have an F functor that can throw multiple exceptions (in the example below Checked and SQLException). I want to be able to call a function with F as an argument, so that any checked F throw exceptions (except for SQLException, which will be handled internally) will be restored.

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 is not. Is there a way to make this work, or is defining type arguments the only way around? Compilation Error:

 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 
+7
java generics
source share
3 answers

There is a strange feature of the Java parser (in jdk 1.8u152 and 9.0.1, but not in 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 instantiates the interface.

You can do this wrong by simply replacing 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!

Relevant part of JLS Section 18.2.5 . But I do not see where this explains the behavior described above.

+6
source share

Sorry for my comment, it didn't actually compile, but it somehow worked on Eclipse. I think a compilation error is really expected. Call Method Signature:

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

and you use it like:

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

With the method signatures, the class of the first parameter (RuntimeException) must match the total number of mayThrow1 (SQLException), since both of them are E in the signature.

+1
source share

When you see public static <T, E extends Exception> T call(Class<E> exceptionClass, SQLExceptionThrowingFunction<Connection, T, E> f) throws E , which reports:

  • type E in Class<E> (your first argument, exceptionClass) and
  • type E in SQLExceptionThrowingFunction<Connection, T, E> f) throws E

must be of the same type / subtype.

Therefore, it is expected that E (i.e. SQLException ) in SQLExceptionThrowingFunction will have a subtype of E (exceptionClass), which is passed as a RuntimeException ). (this happens when you call ConnectionPool.call(RuntimeException.class, Test::<SQLException>mayThrow1);

Since this wait fails, you get a compilation error.

You can verify this by changing ...

  • ConnectionPool.call(RuntimeException.class, Test::<SQLException>mayThrow1); to
  • ConnectionPool.call( .class, fitest.Test::<SQLException>mayThrow1); exception .class, fitest.Test::<SQLException>mayThrow1); which will remove the error on this line.

Not sure if this is your intention initially.

1: What can you do to use common stuff (if you don't want to throw exceptions, this is the method of calling changes, as shown below, and then all your code will work.

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

2: Or you can just call it without specifying a type. eg

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

I am not sure if this solves your question. If you have a different intention when you said Is there a way to get this to work, or is the workaround of specifying the type arguments the only way that you really wanted, then please share the pseduo syntax, as if you wanted the material to work.

0
source share

All Articles