Java教程是针对JDK 8编写的。本页面中描述的示例和实践未充分利用后续版本中引入的改进,并可能使用不再可用的技术。 有关Java SE 9及以后版本中更新的语言特性的摘要,请参阅Java语言更改。 有关所有JDK版本的新功能、增强功能和已删除或弃用选项的信息,请参阅JDK发行说明。
在这里我们可以使用泛型方法:
interface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); // 嘿,类型变量也可以有边界! }
然而,在containsAll和addAll中,类型参数T只使用了一次。返回类型不依赖于类型参数,方法的任何其他参数也不依赖于它(在这种情况下,只有一个参数)。这告诉我们类型参数被用于多态性;它的唯一作用是允许在不同的调用点使用各种实际参数类型。如果是这样的情况,应该使用通配符。通配符旨在支持灵活的子类型化,这正是我们想要表达的。
containsAll
addAll
T
泛型方法允许类型参数用于表达方法的一个或多个参数以及/或其返回类型之间的依赖关系。如果没有这样的依赖关系,就不应该使用泛型方法。
可以同时使用泛型方法和通配符。这是方法Collections.copy()的示例:
Collections.copy()
class Collections { public static <T> void copy(List<T> dest, List<? extends T> src) { ... }
请注意两个参数类型之间的依赖关系。从源列表src复制的任何对象都必须可以赋值给目标列表dst的元素类型T。因此,src的元素类型可以是T的任何子类型-我们不关心具体是哪个子类型。copy的签名使用了一个类型参数来表示依赖关系,但是在第二个参数的元素类型中使用了通配符。
src
dst
copy
我们也可以以另一种方式编写此方法的签名,而不使用通配符:
class Collections { public static <T, S extends T> void copy(List<T> dest, List<S> src) { ... }
这样做也可以,但是第一个类型参数既用于dst的类型,也用于第二个类型参数S的边界,而S本身只在src的类型中使用了一次-没有其他地方依赖于它。这表明我们可以用通配符替换S。使用通配符比显式声明类型参数更清晰和简洁,因此在可能的情况下应该首选通配符。
S
通配符还具有在方法签名之外使用的优点,例如字段、局部变量和数组的类型。以下是一个示例。
回到我们的绘图问题,假设我们想要保存绘图请求的历史记录。我们可以在Shape类内部维护一个静态变量作为历史记录,并且让drawAll()方法将其传入的参数存储到历史记录字段中。
Shape
drawAll()
static List<List<? extends Shape>> history = new ArrayList<List<? extends Shape>>(); public void drawAll(List<? extends Shape> shapes) { history.addLast(shapes); for (Shape s: shapes) { s.draw(this); } }
最后,让我们再次注意类型参数的命名约定。当类型没有更具体的信息来区分时,我们使用T表示类型。这在泛型方法中经常发生。如果有多个类型参数,我们可能会使用与T在字母表中相邻的字母,比如S。如果泛型方法出现在泛型类中,最好避免在方法和类的类型参数中使用相同的名称,以避免混淆。嵌套的泛型类也是如此。
关于 Oracle | 联系我们 | 法律声明 | 使用条款 | 您的隐私权
版权所有 © 1995, 2022 Oracle 及其附属公司。保留所有权利。