你可能知道一个无界的通配符数组如Set<?>能容纳任何类型的元素,同样一个原始的Set也可以。那它们之间有什么不同呢?

关于Set<?>的两个事实

这是两个关于Set<?>的事实:

  • 第一条:因为是用了?来代表任何类型,所以Set<?>可以容纳任何类型的元素。
  • 第二条: 因为我们不知道?是什么类型,所以我们不能把任何类型放入Set<?>

所以Set<?>可以容纳任何类型的元素(满足第一条)。但是我们不能放入任何元素(满足第二条)。这两个条件冲突了吗?当然没有。下面用两个例子来说明:

第一条意味着这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Legal Code
public static void main(String[] args) {
HashSet<Integer> s1 = new HashSet<Integer>(Arrays.asList(1, 2, 3));
printSet(s1);
HashSet<String> s2 = new HashSet<String>(Arrays.asList("a", "b", "c"));
printSet(s2);
}
public static void printSet(Set<?> s) {
for (Object o : s) {
System.out.println(o);
}
}

所以Set<?>可以容纳任何类型的元素,我们简单的使用Object 就可以进行迭代。

第二条意味着如下情况:

1
2
3
4
5
6
7
//Illegal Code
public static void printSet(Set<?> s) {
s.add(10);//this line is illegal
for (Object o : s) {
System.out.println(o);
}
}

因为我们不知道<?> 类型到底是什么,所以我们不能添加别的元素,除非是null。同样的原因,我们不能使用Set<?>进行实例化。代码如下:

1
2
//Illegal Code
Set<?> set = new HashSet<?>();

Set vs. Set<?>

原始类型的Set和无界的通配符的Set<?>到底有什么不同呢?

这样的使用是没问题的:

1
2
3
4
5
6
public static void printSet(Set s) {
s.add("2");
for (Object o : s) {
System.out.println(o);
}
}

因为原始类型是没有限制的。然而,这将容易损坏集合的不变性。

简言之,通配符类型是安全的而原始类却不是。我们不能把任何元素到放入Set<?>中。

什么时候Set<?>比较有用?

当你使用泛型,但是你不知道时间的参数是什么。你可以使用<?>。它只被用作方法的参数。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ublic static void main(String[] args) {
HashSet<Integer> s1 = new HashSet<Integer>(Arrays.asList(1,2,3));
HashSet<Integer> s2 = new HashSet<Integer>(Arrays.asList(4,2,3));
System.out.println(getUnion(s1, s2));
}
public static int getUnion(Set<?> s1, Set<?> s2){
int count = s1.size();
for(Object o : s2){
if(!s1.contains(o)){
count++;
}
}
return count;
}

参考文档

  • Set vs. Set<?>
  • Bloch, Joshua. Effective java. Addison-Wesley Professional, 2008.