化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
?
化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
?
這裏的概念被稱爲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>
(不變量)。
這是因爲當HashMap中可以包含並給E繼承對象,所以你希望能夠通過任何類型的繼承E,對象的集合,而不只是E.
如果是收藏,那麼您將無法通過ArrayList<F>
,例如,其中F擴展E.