从 catch block 的结果调用具有通用参数的 Java 方法

标签 java generics exception try-catch catch-block

我遇到了一个意外问题,涉及异常捕获和签名中的 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!
    }

}

暂且不谈风格不佳的问题(我完全承认更好的方法来完成我在这里所做的事情!),这是一个简化的匿名版本以下业务逻辑:

  • class ResultWrapper 包装对下游服务的调用结果。它要么包含调用的结果,要么包含由此产生的异常
  • WrapperTransformer 类负责以某种方式转换 ResultWrapper(不过,这里的“转换”是空操作)

上面给出的测试失败了。通过调试,我确定这是因为 WrapperBuilder.of(e) 实际上是在调用泛型方法(即 of(final T result))。如果泛型参数是“贪婪的”- RuntimeException T,那(某种程度上)是有道理的,因此该方法是明智的(尽管是无意的)选择。

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

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

然后测试失败 - 即 Exception 被识别为 RuntimeException,非泛型 .of 方法被调用,等等生成的 ResultWrapper 有一个填充的 exc

这让我很困惑。我相信,即使在 catch (Exception e) 子句中,e 仍保留其原始类型(并记录消息 System.out.println(e.getClass( ).getSimpleName() 表明这是真的) - 那么如何改变 catch 的“类型”来覆盖通用方法签名?

最佳答案

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

  • 如果你捕获到一个Exception,静态类型是Exception, 它不是 RuntimeException 的子类,因此泛型 of(Object) 被调用。 (回想一下,T 被翻译成 Object 汇编)。
  • 在捕获 RuntimeException 的情况下,静态类型是 RuntimeException,并且由于它确实适合 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,因为的静态类型>x,编译时已知,是Number,不是Integer的子类。

关于从 catch block 的结果调用具有通用参数的 Java 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37443633/

相关文章:

java - 在 Java 中捕获异常

java - 通过JSP显示BLOB(图片)

java - jirutka/rsql-解析器 : How to define an operator that has no argument?(=空=,=notEmpty=)

java - 使用泛型类型参数调用方法时出现问题

java - 为什么我的 `main()` 没有捕获在 junit 测试中 `timer` 中抛出的异常?

java - 将字符串添加到 ArrayList

c# - 如何确保 T 在固定字节数内是可序列化的?

Java 泛型命名约定

java - 为什么我可以覆盖一些抛出异常的方法而我不能覆盖其他方法?

java - 运行时/已检查/未检查/错误/异常之间的差异