Java 具有从 catch 块的结果调用的通用参数的方法
Java method with Generic argument called from result of catch block
我 运行 遇到了一个意外问题,涉及签名中的异常捕获和 Java 泛型。事不宜迟,有问题的代码(解释如下):
public class WhosebugTest {
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
包装对下游服务的调用结果。它要么包含调用的结果,要么包含结果异常
- class
WrapperTransformer
负责以某种方式转换 ResultWrapper(尽管在这里,"transformation" 是一个空操作)
上面给出的测试失败了。通过调试,我确定这是因为 WrapperBuilder.of(e)
实际上是在调用泛型方法(即 of(final T result)
)。如果通用参数是 "greedy" - a RuntimeException
是 a 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()
表明这是真的) - 那么如何更改 "type" catch 重写泛型方法签名?
调用的方法由参数的静态类型定义。
- 如果你捕捉到一个
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
.
的子类
我 运行 遇到了一个意外问题,涉及签名中的异常捕获和 Java 泛型。事不宜迟,有问题的代码(解释如下):
public class WhosebugTest {
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
包装对下游服务的调用结果。它要么包含调用的结果,要么包含结果异常 - class
WrapperTransformer
负责以某种方式转换 ResultWrapper(尽管在这里,"transformation" 是一个空操作)
上面给出的测试失败了。通过调试,我确定这是因为 WrapperBuilder.of(e)
实际上是在调用泛型方法(即 of(final T result)
)。如果通用参数是 "greedy" - a RuntimeException
是 a 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()
表明这是真的) - 那么如何更改 "type" catch 重写泛型方法签名?
调用的方法由参数的静态类型定义。
- 如果你捕捉到一个
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
.