2016-05-25 40 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))。如果通用參數是「貪婪的」,那麼(有點)是有道理的,因爲這種方法是明智的(儘管是非預期的)選擇。

然而,當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()表明該屬性爲真) - 那麼如何更改catch的「類型」覆蓋泛型方法簽名?

回答

3

被調用的方法由參數的靜態類型定義。

  • 在這種情況下你抓住的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)被調用,即使xInteger,因爲靜態類型的x,這是在編譯知時間,是Number,並且不是Integer的子類。

+0

太棒了,謝謝! – scubbo