简体   繁体   English

从catch块的结果调用带有泛型参数的Java方法

[英]Java method with Generic argument called from result of catch block

I've run into an unexpected issue involving Exception catching and Java generics in signatures. 我遇到了一个意外的问题,涉及异常捕获和签名中的Java泛型。 Without further ado, the code in question (explanation follows): 事不宜迟,有问题的代码(如下解释):

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!
    }

}

Leaving aside, for the moment, questions of bad style (I completely acknowledge that there are better ways to do do what I'm doing here!), this is a trimmed down and anonymised version of the following business logic: 暂且不说,就目前而言,坏作风问题(!我完全承认, 更好的方法去做做我在这里做什么),这是下列业务逻辑的下调和匿名版本:

  • class ResultWrapper wraps the result of a call to a downstream service. ResultWrapper类包装对下游服务的调用结果。 It either contains the result of the call, or the resulting exception 它要么包含调用结果,要么包含结果异常
  • class WrapperTransformer is responsible for transforming the ResultWrapper in some way (though, here, the "transformation" is a no-op) WrapperTransformer类负责以某种方式转换ResultWrapper(尽管这里的“转换”是禁止操作的)

The test given above fails. 上面给出的测试失败。 From debugging, I have determined that this is because WrapperBuilder.of(e) is, in fact, calling the generic method (ie of(final T result) ). 通过调试,我确定这是因为WrapperBuilder.of(e)实际上正在调用泛型方法(即of(final T result) )。 That (sort of) makes sense, if generic arguments are "greedy" - a RuntimeException is a T , so that method is a sensible (though unintended) choice. 如果泛型参数是“贪婪的”,则这(某种)是有意义的RuntimeException T ,因此该方法是明智的(尽管是意外的)选择。

However, when the DownstreamWrapper::getResult method is changed to: 但是,当DownstreamWrapper::getResult方法更改为:

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

then the test fails - ie the Exception is identified as a RuntimeException , the non-generic .of method is called, and so the resulting ResultWrapper has a populated exc . 然后测试失败-即Exception被识别为RuntimeException ,非泛型.of方法被调用,并且因此所得ResultWrapper具有填充exc

This is completely baffling to me. 这完全让我感到困惑。 I believe that, even inside a catch (Exception e) clause, e retains its original type (and logging messages System.out.println(e.getClass().getSimpleName() suggest that is true) - so how can changing the "type" of the catch override the generic method signature? 我相信,即使在catch (Exception e)子句中, e仍保留其原始类型(并且记录消息System.out.println(e.getClass().getSimpleName()表示这是事实)-因此,如何更改“捕获的“类型”会覆盖通用方法签名?

The method that is called is defined by the static type of the argument. 调用的方法由参数的静态类型定义。

  • In the case you catch an Exception , the static type is Exception , which is not a subclass of RuntimeException , so the generic of(Object) is called. 在捕获Exception的情况下,静态类型为Exception ,它不是RuntimeException的子类,因此将调用泛型of(Object) (Recall that T is translated to Object in compilation). (回想一下, T在编译时已转换为Object )。
  • In the case you catch RuntimeException , the static type is RuntimeException , and since it does fit of(RuntimeException) , the more specific method is called. 在捕获RuntimeException的情况下,静态类型为RuntimeException ,并且由于它确实适合of(RuntimeException) ,因此将调用更具体的方法。

Note that e.getClass().getSimpleName() is giving you the Dynamic type, and not the static one. 请注意, e.getClass().getSimpleName()为您提供了动态类型,而不是静态类型。 The dynamic type is unknown during compilation, while which method is invoked is chosen during compile time. 动态类型在编译期间是未知的,而在编译期间选择调用哪种方法。

Here is a simpler code that demonstrates the same issue: 这是一个演示相同问题的简单代码:

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());
}

In here, the method foo(Object) is called, even though x is an Integer , because the static type of x , which is known in compile time, is Number , and is not a subclass of Integer . 在这里,即使xInteger ,也将调用方法foo(Object) ,因为在编译时已知的x的静态类型是Number ,而不是Integer的子类。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM