2016-09-08 22 views
2

所以我遇到了一個奇怪的錯誤,在一個抽象的控制器方法被調用,並傳遞一個類型系統不應該允許的對象。傳遞子類型到Java通用方法與反射拋出沒有例外

這是一個演示錯誤的簡單示例。

import org.junit.Test; 

import java.lang.reflect.InvocationTargetException; 

public class RandomJavaTesting 
{ 
    public static class Animal { 
    } 

    public static class Cat extends Animal { 
    } 

    public static class AnimalManager<T extends Animal> { 

     // This gets called and doesn't blow up 
     public void add(T animal){ 
      // This will throw a class cast exception 
      // Why does this blow up here for CatManager? 
      this.callAdd(animal); 
     } 

     public void callAdd(T animal){ 

     } 
    } 

    public static class CatManager extends AnimalManager<Cat> { 

     @Override 
     public void callAdd(Cat animal) 
     { 
      // This will never run 
     } 
    } 


    @Test 
    public void callingGenericMethodWithSubClassType() throws InvocationTargetException, IllegalAccessException 
    { 

     CatManager manager = new CatManager(); 
     Animal animal = new Animal(); 
     AnimalManager.class.getDeclaredMethod("add", Animal.class).invoke(manager, animal); 

    } 
} 

這個測試給出了以下異常:

java.lang.reflect.InvocationTargetException 
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
     at java.lang.reflect.Method.invoke(Method.java:498) 
     at RandomJavaTesting.callingGenericMethodWithSubClassType(RandomJavaTesting.java:44) 
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
     at java.lang.reflect.Method.invoke(Method.java:498) 
     at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) 
     at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 
     at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) 
     at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 
     at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) 
     at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) 
     at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) 
     at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) 
     at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) 
     at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) 
     at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) 
     at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) 
     at org.junit.runners.ParentRunner.run(ParentRunner.java:363) 
     at org.junit.runner.JUnitCore.run(JUnitCore.java:137) 
     at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117) 
     at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42) 
     at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262) 
     at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84) 
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
     at java.lang.reflect.Method.invoke(Method.java:498) 
     at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) 
Caused by: java.lang.ClassCastException: RandomJavaTesting$Animal cannot be cast to RandomJavaTesting$Cat 
     at RandomJavaTesting$CatManager.callAdd(RandomJavaTesting.java:28) 
     at RandomJavaTesting$AnimalManager.add(RandomJavaTesting.java:20) 
     ... 32 more 

基本上,我的問題是,爲什麼調用add方法不會導致一個ClassCastException。 爲什麼調用callAdd會導致異常? 我明白,通用信息不會在運行時保留,但我的假設是擴展一個類導致泛型類的類型參數被保留。 這是Guava TypeToken背後的機制。或者,至少這是我的想法。

+2

類型擦除後,'add'方法的簽名是'public void add(Animal animal)'。 CatManager'繼承了這個方法,而不是'public void add(Cat animal)'。因此,'addCall'是唯一需要'Cat'的方法,因爲它被該簽名覆蓋。 – 4castle

回答

2

基本上它打破了Liskov的替代原則。

對於添加方法,您可以將類型T視爲Animal,但對於addAll方法,類型被指定爲Cat,因此您無法替換類型爲Animal的Cat類型。

+1

不,你不能「將T型視爲動物」。對於泛型類型系統,CatManager類是AnimalManager的子類型,而AnimalManager 沒有方法add(Animal)。它只有一個'add(Cat)'方法,CatManager繼承它。問題是沒有反射操作查詢'AnimalManager '的'add'方法。相反,'AnimalManager.class'反映了* raw type *'AnimalManager',它顛覆了泛型類型系統。 – Holger

2

你是對的,通用的超類型被保留,但你高估的影響。這隻意味着像Class.getGenericSuperclass()這樣的反射方法將爲您提供信息,這確實是Guava TypeToken背後的機制。

繼承的方法仍然受到類型擦除。順便說一下,如果您將錯誤類型的對象傳遞給Method.invoke,則不會得到ClassCastException,而是IllegalArgumentException。但是,當然,如果您對AnimalManager.class.getDeclaredMethod("add", Animal.class)方法執行反射查詢,並且最終得到的簽名不是add(Animal),則會引起混淆。

這甚至適用於覆蓋方法的場景,例如,當您使用AnimalManager.class.getDeclaredMethod("callAdd", Animal.class).invoke(manager, animal);時,仍然沒有獲得IllegalArgumentException,而是嘗試調用方法addAll(Animal),該方法在嘗試委託給顯式定義的方法addAll(Cat)時產生ClassCastException。此過程涉及的addAll(Animal)方法稱爲橋接方法