Java核心技术 卷1-总结-10
- 通配符类型
- 通配符概念
- 通配符的超类型限定
- 无限定通配符
- 通配符捕获
通配符类型
通配符概念
通配符类型中,允许类型参数变化。 例如,通配符类型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传递给这个方法,这一点很受限制。使用通配符类型可以很简单的解决这个问题:
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> wildcardBuddies = managerBuddies; // OK
wildcardBuddies.setFirst(lowlyEmployee); // compile-time error
对setFirst的调用会产生一个类型错误。具体原因可以观察Pair<? extends Employee>
的实现:
? extends Employee getFirst ()
void setFirst(? extends Employee)
这样将不可能调用setFirst方法。编译器只知道需要某个Employee的子类型,但不知道具体是什么类型。它拒绝传递任何特定的类型。 因为?
不能用来匹配。
使用 getFirst
就不存在这个问题:将 getFirst
的返回值赋给一个 Employee
的引用完全合法。这就是引入有限定的通配符的关键之处。现在已经有办法区分安全的访问器方法和不安全的更改器方法了。
通配符的超类型限定
通配符限定还有一个附加的能力,即可以指定一个超类型限定(supertype bound),如下所示:
? super Manager
这个通配符限制为Manager
的所有超类型。带有超类型限定的通配符的行为与上述介绍的行为相反。可以为方法提供参数,但不能使用返回值。 例如,Pair<? super Manager>
有方法:
void setFirst(? super Manager)
? super Manager getFirst()
编译器无法知道setFirs
t方法的具体类型,因此调用这个方法时不能接受类型为Employee
或Object
的参数。只能传递Manager
类型的对象,或者某个子类型(如Executive)对象。另外,如果调用getFirst
,不能保证返回对象的类型。只能把它赋给一个Object
。
下面是一个示例。有一个经理数组,并且想把奖金最高和最低的经理放在一个Pair对象中。这里Pair的类型:Pair是合理的,Pair也是合理的。
下面的方法将可以接受任何适当的Pair:
public static void minmaxBonus(Manager[] a, Pair<? super Manager> result) {
if (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);
}
带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。
下面是超类型限定的另一种应用。Comparable接口本身就是一个泛型类型。声明如下:
public interface Comparable<T> {
public int compareTo(T other);
}
在此,类型变量指示了other参数的类型。例如,String类实现Comparable,它的compareTo方法被声明为
public int compareTo(String other)
由于Comparable是一个泛型类型,可以这样声明ArrayAlg
类的min
方法:
public static <T extends Comparable<T>> T min(T[] a)
这样写比只使用T extents Comparable
更彻底,并且对许多类来讲,工作得更好。例如,如果计算一个String
数组的最小值,T
就是String
类型的,而String
是Comparable<String>
的子类型。但是,处理一个LocalDate
对象的数组时,会出现一个问题。LocalDate
实现了ChronoLocalDate
,而ChronoLocalDate
扩展了Comparable<ChronoLocalDate>
。因此,LocalDate
实现的是Comparable<ChronoLocalDate>
而不是Comparable<LocalDate>
。
在这种情况下,超类型可以用来进行救助:
public static<T extends Comparable<? super T>> T min(T[] a)...
现在compareTo
方法写成
int compareTo(? super T)
有可能被声明为使用类型T
的对象,也有可能使用T
的超类型。无论如何,传递一个T
类型的对象给compareTo
方法都是安全的。
无限定通配符
可以使用无限定的通配符,例如,Pair<?>
。类型Pair<?>
有以下方法:
? getFirst()
void setFirst(?)
getFirst
的返回值只能赋给一个Object
。setFirst
方法不能被调用,也不能用Object调用。 Pair<?>
和Pair
本质的不同在于:可以用任意Object对象调用原始Pair类的setObject 方法。注意:Pair<?>
可以调用 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)
但是,带有通配符的版本可读性更强。
通配符捕获
编写一个交换成对元素的方法:
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>swapHelpe
r的定义只有在T
指出类型时才有明确的含义。 在这种情况下,并不是一定要使用通配符。因为已经直接实现了没有通配符的泛型方法<T>void swap(Pair<T> p)
。然而,下面看一个通配符类型出现在计算中间的示例:
public static void maxminBonus(Manager[] a, Pair<? super Manager> result) {
minmaxBonus(a, result);
PairAlg.swap(result);// OK--swapHel per captures wildcard type
}
在这里,通配符捕获机制是不可避免的。通配符捕获只有在有许多限制的情况下才是合法的。编译器必须能够确信通配符表达的是单个、确定的类型。