2016-05-25 1 views
2

У меня возникла непредвиденная проблема, связанная с выделением исключений и генериками Java в подписях. Не мудрствуя лукаво, код в вопросе (объяснение следует):Метод Java с общим аргументом, вызванным из результата блока catch

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

} 

Оставляя в стороне, на данный момент, вопросы плохого стиля (я полностью признаю, что есть есть лучшие способов сделать то, что я делаю ! здесь), это урезано и анонимная версия следующего бизнеса-логика:

  • класса ResultWrapper оборачивает результат вызова к потоку службы. Он содержит либо результат вызова, или в результате исключения
  • класс WrapperTransformer отвечает за преобразование ResultWrapper каким-то образом (хотя, здесь, то «преобразование» является не-ор)

Тест данное выше не выполняется. Из отладки я решил, что это потому, что WrapperBuilder.of(e) фактически вызывает общий метод (т. Е. of(final T result)). Это (вроде) имеет смысл, если общие аргументы «жадные» - 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() предполагают, что это правда) - так как можно изменить «тип» улова, переопределить подпись общего метода?

ответ

3

Метод, который вызывается, определяется аргументом статическим.

  • В случае, если вы ловите Exception, статический тип Exception, который не является подклассом RuntimeException, поэтому общая of(Object) называется. (Напомним, что T переведен на Object в сборник).
  • В случае, если вы поймаете RuntimeException, статический тип RuntimeException, и так как он подходит of(RuntimeException), то более конкретный метод называется.

Обратите внимание, что e.getClass().getSimpleName() дает вам тип Dynamic, а не статический. Во время компиляции динамический тип неизвестен, а во время компиляции выбирается этот метод.

Вот простой код, который демонстрирует тот же вопрос:

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.

+0

Фантастический, спасибо! – scubbo