2

我遇到了一个意外问题,涉及签名中的异常捕获和 Java 泛型。事不宜迟,有问题的代码(解释如下):

public class StackOverflowTest {

    private static class WrapperBuilder {
        public static <T> ResultWrapper of(final T result) {
            return new ResultWrapper<>(result);
        }

        public static ResultWrapper of(final RuntimeException exc) {
            return new ResultWrapper<>(exc);
        }
    }

    private static class ResultWrapper<T> {
        private final T result;
        private final RuntimeException exc;

        ResultWrapper(final T result) {
            this.result = result;
            this.exc = null;
        }

        ResultWrapper(final RuntimeException exc) {
            this.result = null;
            this.exc = exc;
        }

        public Boolean hasException() {
            return this.exc != null;
        }

        public T get() {
            if (hasException()) {
                throw exc;
            }
            return result;
        }

    }

    private static class WrapperTransformer {

        public ResultWrapper<Result> getResult(ResultWrapper originalWrappedResult) {
            if (originalWrappedResult.hasException()) {
                try {
                    originalWrappedResult.get();
                } catch (Exception e) {
                    return WrapperBuilder.of(e);
                }
            }
            return originalWrappedResult; // Transformation is a no-op, here
        }
    }

    private static class Result {}

    WrapperTransformer wrapper = new WrapperTransformer();


    @Test
    public void testBehaviour() {
        ResultWrapper wrappedResult = WrapperBuilder.of(new RuntimeException());
        final ResultWrapper<Result> result = wrapper.getResult(wrappedResult);
        assertTrue(result.hasException()); // fails!
    }

}

暂且不说风格不好的问题(我完全承认有更好的方法来做我正在做的事情!),这是以下业务逻辑的精简和匿名版本:

  • ResultWrapper包装调用下游服务的结果。它要么包含调用的结果,要么包含产生的异常
  • WrapperTransformer负责以某种方式转换 ResultWrapper (尽管在这里,“转换”是无操作的)

上面给出的测试失败了。通过调试,我确定这是因为WrapperBuilder.of(e)实际上是调用泛型方法(即of(final T result))。如果泛型参数是“贪婪的”,那(有点)是有道理的—— aRuntimeException a T,所以该方法是一个明智的(尽管不是有意的)选择。

但是,当DownstreamWrapper::getResult方法更改为:

// i.e. explicitly catch RuntimeException, not Exception
} catch (RuntimeException e) {
    return WrapperBuilder.of(e)
}

然后测试失败 - 即被Exception标识为 a RuntimeException,调用非泛型.of方法,因此结果ResultWrapper具有填充的exc.

这对我来说完全莫名其妙。我相信,即使在一个catch (Exception e)子句中,它也会e保留其原始类型(并且日志消息System.out.println(e.getClass().getSimpleName()表明这是真的) - 那么如何更改 catch 的“类型”覆盖通用方法签名?

4

1 回答 1

3

调用的方法由参数的静态类型定义。

  • 在您捕获 的情况下Exception,静态类型是Exception,它不是 的子类,因此调用RuntimeException泛型 。of(Object)(回想一下,在编译T中翻译为Object)。
  • 在你 catch 的情况下RuntimeException,静态类型是RuntimeException,并且因为它确实 fit of(RuntimeException),所以调用更具体的方法。

请注意,这e.getClass().getSimpleName()是给您动态类型,而不是静态类型。动态类型在编译期间是未知的,而在编译期间选择调用哪个方法。

这是一个演示相同问题的更简单的代码:

public static void foo(Object o) { 
    System.out.println("foo(Object)");
}
public static void foo(Integer n) { 
    System.out.println("foo(Integer)");
}
public static void main (String[] args) throws java.lang.Exception {
    Number x = new Integer(5);
    foo(x);
    System.out.println(x.getClass().getSimpleName());
}

在这里,该方法foo(Object)被调用,即使x是,因为在编译时已知Integer的静态类型是,并且不是 的子类。xNumberInteger

于 2016-05-25T17:17:06.157 回答