2011-07-01 119 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.