【IT168 技术文档】在使用 Java™ 语言的泛型时,通配符非常令人困惑,并且最常见的一个错误就是在使用有界通配符的两种形式的其中之一(“? super T” 和 “? extends T”)时出现错误。您出错了吗?别沮丧,即使是专家也会犯这种错误,本月 Brian Goetz 将展示如何避免这个错误。
在 Java 语言中,数组是协变的(因为一个 Integer 同时也是一个 Number,一个 Integer 数组同时也是一个 Number 数组),但是泛型不是这样的(List
有界通配符(一些有趣的 “? extends T” 通用类型说明符)是语言提供的一种工具,用来处理协变性缺乏 — 有界通配符允许类声明方法参数或返回值何时具有协变性(或相反,声明方法参数或返回值何时具有逆变性(contravariant))。虽然了解何时使用有界通配符是泛型较为复杂的方面,但是,使用有界通配符的压力通常都落在库作者的身上,而非库用户。最常见的有界通配符错误就是忘记使用它们,这就限制了类的使用,或是强制用户不得不重用现有的类。
有界通配符的作用
让我们从一个简单的泛型类开始(一个称为 Box 的值容器),它持有一个具有已知类型的值:
public interface Box
public T get();
public void put(T element);
}
由于泛型不具备协变性,Box
清单 1. 通过泛型类利用固有的多态性
Box
Number num = iBox.get();
Box
Integer i = 3;
nBox.put(i);
通过使用简单的 Box 类,使我们确信可以没有协变性,因为在需要实现多态的位置,数据已经具有某种形式,使编译器能够应用适当的子类型规则。
然而,如果希望 API 不仅能够处理 T 类型的变量,还能处理通过 T 泛型化的类型,事情将变得更加复杂。假设希望将一个新的方法添加到 Box,该方法允许获得另一个 Box 的内容并其放到清单 2 所示的 Box 中:
清单 2. 扩展的 Box 接口并不灵活
public interface Box
public T get();
public void put(T element);
public void put(Box
}
这个扩展 Box 的问题是,只能将内容放到类型参数与原 box 完全相同的 Box 中。因此,清单 3 中的代码就不能进行编译:
清单 3. 泛型不具备协变性
Box
Box
nBox.put(iBox); // ERROR
显示一条错误消息,表示无法在 Box
清单 4. 对清单 3 的 Box 类的改进解释了协变性
public interface Box
public T get();
public void put(T element);
public void put(Box box);
}
现在,清单 3 中的代码可以进行编译并执行,因为 put() 的参数现在可以是参数类型为 T 或 T 的子类型的 Box。由于 Integer 是 Number 的子类型,编译器能够解析方法引用 put(Box
很容易犯清单 3 中的 Box 错误,即使是专家也难以避免 — 在平台类库中,许多地方都使用 Collection
下限通配符
上面的大多数有界通配符都进行了限定;“? extends T” 符号为类型添加了一个上限。但是,虽然比较少见,仍然可以使用 “? super T” 符号为类型添加一个下限,表示 “类型 T 以及它的任何超类”。当您希望指定一个回调对象(例如一个比较器)或存放某个值的数据结构,可以使用下限通配符。
假设我们希望增强 Box,使它能够与另一个 box 的内容进行比较。可以通过 containsSame() 方法和 Comparator 回调对象的定义扩展 Box,如清单 5 所示:
清单 5. 尝试向 Box 添加一个比较方法
public interface Box
public T get();
public void put(T element);
public void put(Box box);
boolean containsSame(Box other,
EqualityComparator
public interface EqualityComparator
public boolean compare(T first, T second);
}
}