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
?
答案 0 :(得分: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的实例,请确保您可以将其添加到A列表中(因为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>
(invariace)也不是这样。
答案 1 :(得分:2)
这是因为当HashMap可以包含从E继承的对象时,所以你希望能够传递一个继承E的任何类型的对象集合,而不仅仅是E.
如果是Collection,那么你将无法传递ArrayList<F>
,其中F扩展为E,例如。