2010-11-09 105 views
3

我一直在閱讀有關泛型方法,並認爲我理解泛型類型參數如何約束方法參數類型,但是當我用實際代碼測試了一些想法時,我得到了意想不到的結果。下面是我不明白一個簡單的通用方法:java通用方法如何約束方法類型參數?

private static <T> void foo(T[] t1, T[] t2){ 
    t2[0] = t1[0]; 
} 
... 
String[] stringArray = new String[]{"1", "2", "3"}; 
Integer[] integerArray = new Integer[]{4,5,6}; 
foo(stringArray, integerArray); 

我本來以爲這個通用方法約束,使得兩個陣列必須是同一類型T的,但在上面的代碼的做法即使一個數組的類型是String而另一個數組的類型是Integer,編譯也很好。程序運行時,會生成運行時異常(ArrayStoreException)。

+0

我不是java專家,但我猜分析器不能告訴foo()接受2個類型T [] s必須是相同的。它看到String []是一個T []類型,Integer []也是一個T []類型。 T應該使用哪一個? – Falmarri 2010-11-09 22:02:09

+0

很好的回答。做得好的SO'ers。 – CurtainDog 2010-11-10 01:07:50

+0

@CurtainDog非常感謝我們盡力而爲;-) – 2010-11-10 09:13:05

回答

1

在這個例子中,推斷的類型是? extends Object[],它適合兩種類型。

爲了達到你想要什麼,你需要:

private static <T> void foo(Class<T> clazz, T[] t1, T[] t2); 

然後

foo(String.class, stringArray, stringArray); // compiles 
foo(String.class, stringArray, integerArray); // fails 
1

一個不那麼衆所周知的事實是,String[]Object[]一種亞型(相比之下,List<String>不是的List<Object>亞型)。因此,編譯器可以推斷T = Object,使得該方法簽名

foo(Object[] t1, Object[] t2) 

可與foo(stringArray, integerArray)被調用。

如果您嘗試使用列表相同:

<T> void foo(List<T> t1, List<T> t2) { ... } 

你會發現,

foo(new ArrayList<String>(), new ArrayList<Integer>()) 

不編譯 - 因爲沒有T使得這兩個List<String>List<Integer>List<T>亞型。但是,相反,如果你使用通配符類型聲明方法:

void foo(List<?> t1, List<?> t2) { ... } 

的方法可以調用(但方法體不會編譯,因爲編譯器知道?可參考不兼容的類型)。

+0

字符串擴展對象是一個「不太知名的事實」? – EJP 2010-11-10 01:32:34

+0

數組的協變是令人驚訝的 - 畢竟它違反了Liskov的替換原則(當失敗時,存在運行時檢查會拋出ArrayStoreException),與泛型類型的不變性形成鮮明對比。 – meriton 2010-11-10 18:01:56

1

由於@Bozho已經證明這個問題可以解決,但證明上的是什麼考慮下面的代碼會:

public class Main { 


    // This is the original version that fails because of type erasure in arrays 
    private static <T> void foo(T[] t1, T[] t2) { 

    t2[0] = t1[0]; 
    } 

    // The same method as foo() but with the type erasure demonstrated 
    private static void foo2(Object[] t1, Object[] t2) { 

    // Integer[] should not contain String 
    t2[0] = t1[0]; 
    } 


    public static void main(String[] args) { 

    String[] stringArray = new String[]{"1", "2", "3"}; 
    Integer[] integerArray = new Integer[]{4, 5, 6}; 

    foo2(stringArray, integerArray); 
    } 

} 

這是所有的事實,在Java數組是協變的,而仿製藥做不是。 There is an interesting article about it here。快速報價說明了一切:

數組在Java語言中是 協變的 - 這意味着如果 Integer擴展號(它 如此),那麼不僅是一個整數 也有不少,但Integer []是 也是Number [],並且您可以通過 pass或指定一個Integer []來調用 Number []。 (更多 正式,如果Number是超類型 整型,則Number []是Integer []的超類型 )。您可能認爲 同樣適用於泛型類型以及 - 該List是超類型列表,並且您可以通過 列表,其中列表爲 。不幸的是,它不是 這樣工作。

事實證明,它有一個很好的理由它 不會這樣工作:它會打破 類型安全仿製藥應該提供 提供。想象一下,您可以將一個 列表分配給列表。然後 下面的代碼將讓你 放東西,是不是一個整數 到列表:

List<Integer> li = new ArrayList<Integer>(); 
List<Number> ln = li; // illegal 
ln.add(new Float(3.1415)); 

因爲LN是一個列表,添加一個Float似乎完全合法的。但是,如果ln與li混疊在一起,那麼它將打破li的定義中隱含的類型安全承諾 - 它是一個整數列表,這就是爲什麼泛型不能協變的原因。