HashSet构造函数中的通配符泛型

时间:2011-07-01 10:14:30

标签: java generics wildcard hashset

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

2 个答案:

答案 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,例如。