2011-07-01 117 views
1

化java HashSet實施有一個構造函數:通配符泛型

public HashSet(Collection<? extends E> c) { 
    map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16)); 
    addAll(c); 
} 

爲什麼它是Collection<? extends E> c?這還不夠:Collection<E> c

回答

5

這裏的概念被稱爲variance(協方差,逆變)。

比方說,你有以下兩類:

class A {} 
class B extends A {} 

在這種情況下,你可以說,B一個實例是A一個實例。換句話說,下面的代碼是完全有效的:

A instance = new B(); 

現在,Java中的泛型類默認情況下是不變的。這意味着List<B>不是List<A>。換句話說,下面的代碼不會編譯:

List<A> as = new ArrayList<B>(); // error - Type mismatch! 

但是,如果你有B的一個實例,相信你可以將其添加到列表(因爲B擴展A):

List<A> as = new ArrayList<A>(); 
as.add(new B()); 

現在,讓我們說你有通過消耗它的實例與A的交易清單的方法:

void printAs(List<A> as) { ... } 

很容易將做出以下電話:

List<B> bs = new ArrayList<B>(); 
printAs(bs); // error! 

但是,它不會編譯!如果您想要進行此類調用,則必須確保參數List<B>是該方法預期類型的​​子類型。這是通過使用協方差完成:

void printAs2(List<? extends A> as) { ... } 
List<B> bs = new ArrayList<B>(); 
printAs2(bs); 

現在,這種方法利用List<? extends A>一個實例,這是事實,List<B> extends List<? extends A>,因爲B extends A。這是協方差的概念。


這個介紹之後,我們可以回去的HashSet的構造函數中你提到:

public HashSet(Collection<? extends E> c) { ... } 

這意味着下面的代碼將工作:

HashSet<B> bs = new HashSet<B>(); 
HashSet<A> as = new HashSet<A>(bs); 

它的工作原理因爲HashSet<B> is a HashSet<? extends A>

如果構造函數被聲明爲HashSet(Collection<E> c),那麼第二行就不會編譯,因爲即使是HashSet<E> extends Collection<E>,它也不是真的HashSet<B> extends HashSet<A>(不變量)。

2

這是因爲當HashMap中可以包含並給E繼承對象,所以你希望能夠通過任何類型的繼承E,對象的集合,而不只是E.

如果是收藏,那麼您將無法通過ArrayList<F>,例如,其中F擴展E.