固定的泛型类型系统使用起来并没有那么令人愉快。Java的设计者发明了一种巧妙(仍然是安全的)“解决方案”:通配符类型。
例如:Pair<? extends B>,表示任何泛型Pair类型,它的类型参数是B的子类,如Pair<BSub>,但不是Pair<Date>。
构造一个方法:
public static void executeFun(Pair<BSub> p){
p.getFirst().fun();
p.getSecond().fun();
}
p.getFirst().fun();
p.getSecond().fun();
}
不能将Pair<B>传给这个方法,方法的功能受到了很大的限制。解决方法是:使用通配符类型。
public static void executeFun(Pair<? extends B> p){
...
}
类型Pair<BSub>是Pair<? extends B>的子类型。
使用通配符会通过Pair<? extends B>的引用破坏Pair<BSub>吗?答案是不能。
Pair<BSub> bsp = new Pair<BSub>();
Pair<? extends B> bp = bsp;//ok
bp.setFirst(new B());//Error
对setFirst的调用有一个类型错误。找知道其中缘由,请仔细看看类型Pair<? extends B>。它的方法如下所示:
? extends B getFirst();
void setFirst(? extends B);
这样不可能调用setFirst方法。编译器只知道它需要某个B类型的字类型,但不知道具体是什么类型。它拒绝传递任何特定的类型---毕竟,?不能用来匹配。
使用getFirst就不存在这个问题:将getFirst的返回值赋给一个B的引用是完全合法的。
这就是引入有限定的通配符的关键之处。现在已经有办法区分安全的访问器方法和不安全的更改器方法了。
通配符的超类型限定
通配符限定与类型变量限定十分相似。但是,它还有一个附加的能力,即可以指定一个超类型限定,如下所示:
? super BSub
这个通配符限制为BSub的所有超类型。已有的super关键字十分准确的描述了这种关系。
为什么要这样做?带有超类型限定的通配符的行为与前面介绍的相反。可以向方法提供参数,但不能使用返回值。例如,Pair<? super BSub>有方法
void setFirst(? super BSub)
? super BSub getFirst()
编译器不知道setFirst方法的确切类型,但是可以用任意BSub对象(或子类型)调用它,而不能用B对象调用。然而,如果调用getFirst,返回的对象类型不会得到保证,只能把它赋给一个Object,如果要将返回值赋给一个非Object的变量要使用强制的类型转换。直观地讲,带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。
下面是超类型限定的另一种应用。Comparable接口本身就是一个泛型类型。它被声明如下:
public interface Comparable<T> {
public int compareTo(T o);
}
在此,类型变量指示了o参数的类型。例如,String类实现Comparable<String>,它的compareTo方法被声明为
public int compareTo(T o);
}
在此,类型变量指示了o参数的类型。例如,String类实现Comparable<String>,它的compareTo方法被声明为
public int compareTo(String o)
很好,显式的参数有一个正确的类型。在JDK1.5之前,o是一个Object,并且该方法的实现需要强制的类型转换。因为Comparable是一个泛型类型,也许可以把ArrayAlg类的min方法做得更好一些?可以这样声明:
public static <T extends Comparable<T>> T min(T[] a){
if(a == null || a.length == 0){
return null;
}
T t = a[0];
for(int i=1;i<a.length;i++){
if(t.compareTo(a[i]) > 0){
t = a[i];
}
}
return t;
}
if(a == null || a.length == 0){
return null;
}
T t = a[0];
for(int i=1;i<a.length;i++){
if(t.compareTo(a[i]) > 0){
t = a[i];
}
}
return t;
}
看起来,这样写只适用T extends Comparable更彻底,并且对于许多类来讲会工作得更好。例如,如果计算一个String数组的最小值,T就是String类型的,而String是Comparable<String>的字类型。但是,当处理一个GregorianCalendar对象的数组时,就会出现问题。GregorianCalendar是Calendar的子类,并且Calendar实现了Comparable<Calendar>。因此GregorianCalendar实现的是Comparable<Calendar>,而不是Comparable<GregorianCalendar>。
在这种情况下,超类型可以用来进行救助:
public static <T extends Comparable<? super T>> T min(T[] a){ ... }
现在compareTo(? super T)
有可能它被声明为使用类型T的对象,也有可能使用T的超类型(例如,当T是GregorianCalendar)。无论如何,传递一个T类型的对象给compareTo方法都是安全的。
对于初学者来说,<T extends Comparable<? super T>>这样的声明看起来有点吓人。但很遗憾,因为这一声明的意图在于帮助应用程序员排除调用参数上的不必要的限制。对泛型没有兴趣的应用程序员可能很快就学会掩盖这些声明,想当然地认为库程序员做的都是正确的。如果是一名库程序员,一定要习惯于通配符,否则,就会受到用户的责备,还要在代码中随意地添加强制类型转换直至代码可以编译。
无限定通配符
还可以使用无限定的通配符,例如,Pair<?>。咋看起来好像和原始的Pair类型一样。实际上,有很大的不同。类型Pair<?>有方法如:
? getFirst()
void setFirst(?)
getFirst的返回值只能赋给一个Object。setFirst方法不能被调用,甚至不能用Object调用。Pair<?>和Pair的本质不同在于:可以用任意的Object对象调用原始的Pair类的setFirst()方法。
为什么要使用这样脆弱的类型?它对于许多简单的操作非常有用。例如,下面这个方法将用来测试一个pair是否包含了指定的对象,它不需要实际的类型。
public static boolean hasNulls(Pair<?> p){
return p.getFirst() == null || p.getSecond() == null;
}
return p.getFirst() == null || p.getSecond() == null;
}
如果把它转化成泛型方法,可以避免使用通配符类型:
public static<T> boolean hasNulls(Pair<T> p)
但是,带有通配符类型的版本可读性更强。