12.8 通配符类型
通配符类型Pair<? extends Employee>表示任何泛型Pair类型,它的类型参数是Employee的子类,如Pair<Manager>,但不是Pair<String>.假设要编写一个打印雇员对的方法,像这样:
public static void printBuddies(Pair<Employee> p)
{
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + " and " + second.getName() + " are buddies.";
}
正如前面讲到的,不能将Pair<Manager>传递给这个方法.这一点很受限制,解决的方法很简单:使用通配符类型:
public static void printBuddies(Pair<? extends Employee> p)
类型Pair<Manager>是Pair<? extends Employee>的子类型.
使用通配符会通过Pair<? extends Employee>的引用破坏Pair<Manager>吗?
Pair<Manager> managerBuddies = new Pair<>(ceo, cfo);
Pair<? extends Employee> wildcarcBuddies = managerBuddies; // ok
wildcardBuddies.setFirst(lowlyEmployee);
这可能不会引起破坏.仔细看一看类型Pair<? extends Employee> 其方法似乎是这样的:
? extends Employee getFirst()
void setFirst(? extends Employee)
这样将
不可能调用setFirst方法.编译器只知道需要某个Employee的子类型,但不知道具体是什么类型.它拒绝传递任何特定的类型,毕竟?不能用来匹配.使用getFirst就不存在这个问题:将getFirst的返回值赋给一个Employee的引用完全合法.
这就是引入有限定的通配符的关键之处,现在已经有办法区分安全的访问器和不安全的更改器的方法了.
12.8.1 通配符的超类型限定
通配符限定与类型变量限定十分类似,但是,还有一个附加的能力,即 可以指定一个超类型限定(supertype bound),如下所示:? super Manager
这个通配符限制为Manager的所有超类型.
为什么要这样做呢?带有超类型限定的通配符的行为与12.8节介绍的相反,可以为方法提供参数,但不能使用返回值.例如,Pair<? super Manager>有方法
void setFirst(? super Manager)
? super Manager getFirst()
编译器不知道setFirst方法的确切类型,但是可以用任意Manager对象调用它,而不是能用Employee对象调用.然而,如果调用getFirst,返回的对象类型就不会得到保证.只能把它赋给一个Object.
直观地讲, 带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取.
下面是超类型限定的另一种应用.Comparable接口本身就是一个泛型类型.声明如下:
public interface Comparable<T>
{
public int compareTo(T other);
}
在此,类型变量指示了other参数的类型.
由于Comparable是一个泛型类型,也许可以把ArrayAlg类的min方法做的更好一些,可以这样声明:
public static <T extends Comparable<T>> T min(T[] a)
看起来,这样写比只使用T extends Comparable更彻底,并且对许多类来讲,工作得更好.
12.8.2 无限定通配符
还 可以使用无限定的通配符,例如,Pair<?>.初看起来,这好像与原始的Pair类型一样.实际上,有很大不同,类型Pair<?>有方法如下所示:? getFirst()
void setFirst(?)
getFirst的返回值只能赋给一个Object,setFirst方法不能被调用,甚至不能用Object调用.
Pair<?>和Pair本质的不同在于:可以用任意Object对象调用原始的Pair类的setObject方法.
注释:可以调用setFirst(null).
为什么要使用这样脆弱的类型?它对于许多简单的操作非常有用.例如,下面这个方法将用来测试一个Pair是否包含一个 null 引用,它不需要实际的类型
public static boolean hasNulls(Pair<?> p)
{
return p.getFirst() == null || p.getSecond() == null;
}
通过将hasNulls转换成泛型方法,可以避免使用通配符类型:
public static <T> boolean hasNulls(Pair<T> p)
但是,带有通配符的版本可读性更强.
12.8.3 通配符捕获
编写一个交换一个Pair元素的方法:public static void swap(Pair<?> p)
通配符不是类型变量,因此,不能在编写代码中使用"?"作为一种类型.也就是说,下述代码是非法的:
? t = p.getFirst(); // error
p.setFirst(p.getSecond());
p.setSecond(t);
这是一个问题,因为在交换的时候必须临时保存第一个元素.幸运的是,这个问题有一个有趣的
解决方法,可以写一个辅助方法法swapHelper,如下所示:
public static <T> void swapHelper(Pair<T> p)
{
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
注意,swapHelper是一个泛型方法,而swap不是,它具有固定的Pair<?>类型的参数.
现在可以由swap调用swapHelper:
public static void swap(Pair<?> p) { swapHelper(p); }
在这种情况下,swapHelper方法的参数T捕获通配符.它不知道是哪种类型的通配符,但是,这是一个明确的类型,并且<T>swapHelper的定义只有在T指出类型时才有明确的含义.
pair3/PairTest3.java如下所示:
package pair3;
public class PairTest3
{
public static void main(String[] args)
{
Manager ceo = new Manager("Gus Greedy", 80000, 2003, 12, 15);
Manager cfo = new Manager("Sid Sneaky", 60000, 2003, 12, 11);
Pair<Manager> buddies = new Pair<>(ceo, cfo);
printBuddies(buddies);
ceo.setBonus(100000);
cfo.setBonus(500000);
Manager[] managers = {ceo, cfo};
Pair<Employee> result = new Pair<>();
minmaxBonus(managers, result);
System.out.println("first: " + result.getFirst().getName() + ", second: " + result.getSecond().getName());
maxminBonus(managers, result);
System.out.println("first: " + result.getFirst().getName() + ", second: " + result.getSecond().getName());
}
public static void printBuddies(Pair<? extends Employee> p)
{
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + " and " + second.getName() + " are buddies.");
}
public static void minmaxBonus(Manager[] a, Pair<? super Manager> result)
{
if (a == null || a.length == 0)
return ;
Manager min = a[0];
Manager max = a[0];
for (int i = 1; i < a.length; i++)
{
if (min.getBonus() > a[i].getBonus())
min = a[i];
if (max.getBonus() < a[i].getBonus())
max = a[i];
}
result.setFirst(min);
result.setSecond(max);
}
public static void maxminBonus(Manager[] a, Pair<? super Manager> result)
{
minmaxBonus(a, result);
PairAlg.swapHelper(result);
}
}
class PairAlg
{
public static boolean hasNulls(Pair<?> p)
{
return p.getFirst() == null || p.getSecond() == null;
}
public static void swap(Pair<?> p) { swapHelper(p); }
public static <T> void swapHelper(Pair<T> p)
{
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
}
运行结果如下所示: