2009-01-17 48 views
7

我今天遇到了一個情況,那就是Java沒有調用我期望的方法 - 這是最簡單的測試用例:(我很抱歉,這看起來很有意思 - 「真實世界」的場景要複雜得多, 「你爲什麼會這麼做?」的立場。)Java方法調度如何與泛型和抽象類一起工作?

我特別感興趣的是爲什麼會發生這種情況,我不在乎重新設計的建議。我有一種感覺,這是在Java Puzzlers中,但我沒有我的副本。

參見下面的試驗<Ť> .getValue()內讚揚具體問題:

public class Ol2 { 

    public static void main(String[] args) { 
     Test<Integer> t = new Test<Integer>() { 
      protected Integer value() { return 5; } 
     }; 

     System.out.println(t.getValue()); 
    } 
} 


abstract class Test<T> { 
    protected abstract T value(); 

    public String getValue() { 
     // Why does this always invoke makeString(Object)? 
     // The type of value() is available at compile-time. 
     return Util.makeString(value()); 
    } 
} 

class Util { 
    public static String makeString(Integer i){ 
     return "int: "+i; 
    } 
    public static String makeString(Object o){ 
     return "obj: "+o; 
    } 
} 

從該代碼的輸出是:

obj: 5 

回答

6

否,的值的類型是不可在編譯時使用。請記住,javac只會編譯一個用於所有可能的T的代碼副本。鑑於此,編譯器在getValue()方法中使用的唯一可能類型是Object。

C++是不同的,因爲它最終會根據需要創建多個編譯版本的代碼。

+0

啊......我以爲Java泛型編譯得更像C++模板。謝謝! – rcreswick 2009-01-17 01:51:47

2

因爲關於makeString()使用的決定是在編譯時進行的,並且基於T可以是任何事物的事實,所以必須是Object版本。想想看。如果您確實Test<String>它將不得不打電話Object版本。因此Test<T>的所有實例將使用makeString(Object)。現在,如果你不喜歡的東西

public abstract class Test<T extends Integer> { 
    ... 
} 

事情可能會有所不同。

2

Josh Bloch的有效的Java有一個很好的討論,澄清出現的混亂,因爲派生對於重載和重載(在子類)方法中的工作方式不同。選擇超載方法---這個問題的主題---是在編譯時確定的; 覆蓋方法中選擇是在運行時完成(並因此得到特定類型的對象的知識。)

的書比我的評論更清楚:見 「第41條:使用重載明智」