区别1
对于Collection<T>
编译器会将T推断为传入的具体类型,而对于Collection<?>
编译器会把?推断为未知类型,此时调用add(new Object())
方法编译无法通过,因为add()
方法接收的参数应为未知类型的子类,而未知类型到底是什么类型没法知道,所以不能传入任何对象,唯一的例外是null,因为null可以是任意类型。
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译报错
c.add(null);
区别2
另一方面,对于Collection<?>
可以调用get()
方法,尽管不知道返回值类型,但无论如何都可以将其赋给一个Object变量。
考虑打印一个列表的两种写法:
public static void printL(List<?> list){
for (Object o : list) {
System.out.println(o);
}
}
public static <T> void printL2(List<T> list){
for (T o : list) {
System.out.println(o);
}
}
对于第二种的写法,留意到T在方法签名中只出现了一次,也就是说T没有用于表达任何实参类型、返回值类型和异常类型相互之间的依赖关系,如果不存在这种相互依赖的关系,使用泛型方法是一种糟糕的代码风格,应该使用通配符。而当存在这些依赖关系的时候,应该用类型参数T,不能通配符。
举一些例子:
Collection接口里的toArray方法使用了类型参数,反映参数a和返回值之间类型关系
<T> T[] toArray(T[] a);
而Collection接口里的addAll方法使用了通配符
boolean addAll(Collection<? extends E> c);
Comparator接口里的compare方法使用了类型参数,反映两个参数o1和o2之间的类型关系
int compare(T o1, T o2);
区别3
类型参数不能指定下界,而通配符可以用super指定下界
public class MyClass {
class C {}
// C是上界
<T extends C> void genericMethod1(Collection<T> c){}
// C是下界
<T super C> void genericMethod2(Collection<T> c){} //编译报错
// C是上界
void genericMethod3(Collection<? extends C> c){}
// C是下界
void genericMethod4(Collection<? super C> c){}
}
区别4
类型参数的上界可以是一个类,也可以有多种边界(multiple bounds),即上界可以是一个类/接口后面跟多个接口构成的交集类型(intersection type),而通配符的上界只能是一个类
public class MyClass {
class C {}
interface I1 {}
interface I2 {}
<T extends C> void genericMethod5(Collection<T> c){}
<T extends C & I1 & I2> void genericMethod6(Collection<T> c){}
<T extends I1 & I2> void genericMethod7(Collection<T> c){}
void genericMethod8(Collection<? extends C> c){}
void genericMethod9(Collection<? extends C & I1 & I2> c){} //编译报错
}
区别5
类型参数可以用于泛型类的签名,通配符不可以
public class MyClass<T> {}
public class MyClass<?> {} //编译报错
区别6
类型参数可以声明变量,通配符不可以
public class MyClass<T> {
T t;
? t; //编译报错
void myMethod1(E e){}
void myMethod2(? e){} //编译报错
}
参考
https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.10、
https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html