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,

Другие вопросы по тегам