2011-05-16 45 views
6

我認爲我對Java泛型有一些很好的理解。當類是泛型時Java通配符的奇怪行爲

This code DOES NOT COMPILE我知道爲什麼。

我們可以通過測試方法只動物類型的列表或它的超類型(如對象的列表)

package scjp.examples.generics.wildcards; 

import java.util.ArrayList; 
import java.util.List; 

class Animal {} 
class Mammal extends Animal {} 
class Dog extends Mammal {} 

public class Test { 

    public void test(List<? super Animal> col) { 
     col.add(new Animal()); 
     col.add(new Mammal()); 
     col.add(new Dog()); 
    } 

    public static void main(String[] args) { 
     List<Animal> animalList = new ArrayList<Animal>(); 
     List<Mammal> mammalList = new ArrayList<Mammal>(); 
     List<Dog> dogList = new ArrayList<Dog>(); 

     new Test().test(animalList); 
     new Test().test(mammalList); // Error: The method test(List<? super Animal>) in the type Test is not applicable for the arguments (List<Mammal>) 
     new Test().test(dogList); // Error: The method test(List<? super Animal>) in the type Test is not applicable for the arguments (List<Dog>) 

     Dog dog = dogList.get(0); 
    }   
} 

但是這裏來了奇怪的一部分(至少對我來說)。

如果我們只會增加<牛逼>聲明類測試作爲通用的,那麼它編譯!並拋出java.lang.ClassCastException:

public class Test<T> { 
... 
} 

Exception in thread "main" java.lang.ClassCastException: scjp.examples.generics.wildcards.Animal cannot be cast to scjp.examples.generics.wildcards.Dog 

我的問題是,爲什麼將通用類型<牛逼>(未在任何地方使用)引起的類編譯和改變通配符的行爲?

回答

7

表達式new Test()是原始類型。 Java語言規範defines類型原始類型的成員的如下:

的類型構造函數(§8.8),實例方法(§8.8,第9.4節),或者非靜態字段(§8.3)未從其超類或超接口繼承的原始類型C的M是在對應於C的泛型聲明中刪除其類型。原始類型C的靜態成員的類型與其在泛型聲明中的類型相同對應於C.

List<? super Animal>的消除是List

此定義背後的基本原理可能是原始類型旨在用作從非泛型遺留代碼使用泛型類型的手段,其中類型參數從不存在。它們沒有設計,並且不是最優的,沒有指定類型參數;這就是通配符類型的用途,即如果你的編譯器合規性水平大於1.5的代碼,你應該寫

Test<?> test = makeTest(); 
    test.test(animalList); 
    test.test(mammalList); 
    test.test(dogList); 

歡喜(或詛咒,因爲這件事而定)再次seing編譯錯誤。

+0

+1。我應該補充一點,即使它在Eclipse中編譯,它肯定會產生一系列可怕的警告,您應該注意這些警告。 – 2011-05-16 21:42:12

+0

實際上不只是eclipse,該規範強制要求警告:「如果刪除更改了方法或構造函數的任何參數的任何類型,則對原始類型的方法或構造函數的調用將生成未經檢查的警告。通常,在運行時幾乎所有可能導致*堆污染*的代碼都需要在編譯時生成未經檢查的警告。 – meriton 2011-05-16 21:55:10

1

有趣的問題。

我通過爲自己編譯確認了你的結果,如果你添加了未使用的類型參數,它確實會編譯(帶有警告)。然而,它未能重新編譯,如果你確實指定類型參數類型:

new Test<Object>().test(animalList); 
    new Test<Object>().test(mammalList); 
    new Test<Object>().test(dogList); 

我懷疑是,因爲你使用的是未經檢查的操作來構建測試對象,編譯器不會刻意去檢查其他參數類型並將整個事件視爲未檢查/不安全。當您指定類型時,它會恢復到以前的行爲。

0

如果添加<T>到您的測試類,然後填充通用的東西編譯錯誤將返回

new Test<String>().test(mammalList); 

我的猜測是,由於測試一般是沒有定義,編譯器決定它不具有足夠的信息來檢查超出該級別的任何內容

1

您通過添加參數類型:

public class Test<T> { 

但你這樣做把它作爲原始類型:

new Test() 

所以所有的賭注現已關閉。爲了實現與遺留代碼的互操作性,編譯器正在讓它通過,但現在不是類型檢查。它會生成一個編譯器警告。