通配符概念

通配符类型中,允许类型参数变化。例如,通配符类型,子类型限定:

  1. pair<? extends Employee>

表示任何泛型 Pair 类型,它的类型参数是 Employee 或 Employee 的子类,如 Pair,但不是 Pair
假设要编写一个打印雇员对的方法,像这样:

  1. public static void printBuddies(Pair<Employee> p) {
  2. Employee first = p.getFirst();
  3. Employee second = p.getSecond();
  4. System.out.println(first.getName() + " and " + second.getName() + " are buddies.");
  5. }

不能将 Pair 传递给这个方法,这一点很受限制。解决的方法很简单:使用通配符类型:

  1. public static void printBuddies(Pair<? extends Employee> p) {

这样 Pair 是 Pair<?extends Employee> 的子类型
chapter_VIII-extends_wildcard
这样我们就可以在方法中使用 ? extends

  1. public static add(Pair<? extends Number> p) {
  2. Number first = p.getFirst();
  3. Number second = p.getSecond();
  4. p.setFirst(new Integer(first.intVlaue() + 100)); // compile-time error
  5. p.setSecond(new Integer(second.intVlaue() + 100)); // compile-time error
  6. return p.getFirst().intValue() + p.getSecond().intValue();
  7. }

可以看到是可以使用 get 方法的,因为你的限定符是属于 Number 或 Number 的子类,可以安全的赋值给 Number。但是 set 方法却报错了,根据上述代码,我们如果传入一个 Pair,显然它是满足 <? extends Number> 的,但是,PairsetFirst() 显然无法接受 Integer 类型。
对于 set 方法,编译器只知道需要某个 Number 的子类型,但又不知道具体是那个类型。编译器拒绝这样的方式,但是使用 get 方法就不存在这个问题。

set 方法中唯一的例外是可以传入 null

我们可以看到可以用 extends 通配符表示可以读,不能写。

通配符的超类型限定

还有另外一个限定是超类型限定:

  1. ? super Manager

这个通配符限制为 Manager 或 Manager 的所有超类型。
chapter_VIII-super_wildcard
带有超类型限定的通配符的子类型限定的通配符介绍的相反。可以为方法提供参数,但不能使用返回值。
可以来看一个例子:

  1. static void setSame(Pair<? super Integer> p, Integer n) {
  2. p.setFirst(n);
  3. p.setSecond(n);
  4. }

因为是 <? super Integer> 因此可以安全的传入 Integer,不仅如此,p 也能传入 Pair 和 Pair 。因为 Number 和 Object 是 Integer 的父类。
但是 get 方法就不行了:

  1. static void getF(Pair<? super Integer> p) {
  2. Integer x = p.getFirst() // Error
  3. }

如果传入的是 Pair ,编译器无法将 Number 类型转换为 Integer。

在超类型限定中,可以使用 Object 接受 get 方法

简单的说用 super 通配符表示只能写,不能读。
超类型限定还有另一中应用。Comparable 接口本身就是一个泛型类型。声明如下:

  1. public interface Comparable<T> {
  2. public int compareTo(T other);
  3. }

现在有这样一个泛型方法,求数组中的最小数,我们规定必须,传入的类型变量必须实现了 Comparable,就可以这样声明:

  1. public static <T extends Comparable<T>> T min(T[] a)

看起来,这样写比只使用 T extents Comparable 更彻底,并且对许多类来讲,工作得更好。例如,如果计算一个 String 数组的最小值,T 就是 String 类型的,而 String 是 Comparable 的子类型。但是,处理一个 LocalDate 对象的数组时,会出现一个问题。LocalDate 实现了 ChronoLocalDate,而 ChronoLocalDate 扩展了 Comparable。因此,LocalDate 实现的是 Comparable 而不是 Comparable
所以要加上超类型:

  1. public static <T extends Comparable<? super T>> T min(T[] a)

现在的 compareTo 方法就成了:

  1. in compareTo(? super T)

有可能被声明为使用类型T的对象,也有可能使用 T 的超类型(如当 T 是 LocalDate,T 的一个子类型)。无论如何,传递一个 T 类型的对象给 compareTo 方法都是安全的。

无限定通配符

还有一种 无限定通配符 :Pair<?>。
无限定通配符既不能读,也不能写。get 方法只能赋值给 Object。set 方法只能使用 null 调用。
为什么要使用这样脆弱的类型?它对于许多简单的操作非常有用。例如,下面这个方法将用来测试一个pair是否包含一个null引用,它不需要实际的类型。

  1. public static boolean hasNulls(Pair<?> p) {
  2. return p.getFist() == null || p.getSecond() == null;
  3. }

通过将 hasNulls 转换成泛型方法,可以避免使用通配符类型:

  1. public static <T> boolean hasNull(Pair<T> p)

但是,带有通配符的版本可读性更强。

通配符捕获

编写一个交换成对元素的方法:

  1. public static void swap(Pair<?> p)

通配符不是类型变量,因此,不能在编写代码中使用 ? 作为一种类型。也就是说,下述代码是非法的:

  1. ? t = p.getFirst(); // Error
  2. p.setFirst(p.getSecond());
  3. p.setSecond(t);

我们可以写一个辅助泛型方法 swapHelper 来解决来捕获通配符:

  1. public static <T> void swapHelper(Pair<T> p) {
  2. T t = p.getFirst();
  3. p.setFirst(p.getSecond());
  4. p.setSecond(t);
  5. }

任何,就可以在 swap 中调用 swapHelper:

  1. public static void swap(Pair<?> p) { swapHelper(p); }