化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.