通用异常类型

时间:2017-11-02 20:30:34

标签: 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);
    }
}

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

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

3 个答案:

答案 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告诉:

  • EClass<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语法,您希望如何使用这些内容。