Java-метод с общим аргументом, вызванным из результата блока catch
Я столкнулся с неожиданной проблемой, связанной с перехватом исключений и обобщением Java в сигнатурах. Без дальнейших церемоний, рассматриваемый код (пояснение следует):
public class StackruTest {
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)
). Это (вроде) имеет смысл, если общие аргументы являются "жадными" - 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()
предположить, что это правда) - так как изменение "типа" улова может переопределить сигнатуру общего метода?
1 ответ
Вызываемый метод определяется статическим типом аргумента.
- В случае, если вы поймали
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
,