在本节中,我们将展示如何使用 Comparable <T>
接口来查找集合中的最大元素。我们将从一个简化版本开始。 在集合框架中找到的实际版本有一个更复杂一点的类
型签名,稍后我们将看到为什么。
以下代码可以从类 Collections
中找到非空集合中的最大元素:
public static <T extends Comparable<T>> T max(Collection<T> coll) {
T candidate = coll.iterator().next();
for (T elt : coll) {
if (candidate.compareTo(elt) < 0) candidate = elt;
}
return candidate;
}
我们首先看到了在 1.4
节签名中声明新类型变量的泛型方法。 例如,asList
方法接受一个 E []
类型的数组并返回 List <E>
类型的结果,并且对于任何
类型 E
都是这样。 这里我们有一个泛型方法来声明类型变量的边界。 方法 max
采用 Collection <T>
类型的集合并返回 T
,并且它对任何类型 T
执行
此操作,使 T
成为 Comparable <T>
的子类型。
在类型签名开头的尖括号中突出显示的短语声明了类型变量 T
,并且我们说 T
由 Comparable <T>
限定。 和通配符一样,即使边界是一个接口而不是一个类,
那么类型变量的边界总是用关键字 extends
来表示,就像这里的情况一样。 与通配符不同,类型变量必须始终使用 extends
,而不是 super
。
在这种情况下,边界是递归的,因为 T
本身的边界取决于 T
。 甚至可以有相互递归的界限,比如
<T extends C<T,U>, U extends D<T,U>>
9.5
节中会出现一个相互递归界限的例子。
方法主体选择集合中的第一个元素作为最大值的候选项,然后将候选项与集合中的每个元素进行比较,当元素较大时将候选项设置为元素。我们使用
iterator().next()
而不是 get(0)
获取第一个元素,因为 get
不是在列表以外的集合上定义的。当集合为空时,该方法引发 NoSuchElement
异常。
当调用方法时,T
可以选择为 Integer
(因为 lteteger
实现 Comparable <Integer>
)或 String
(因为 String
实现了
Comparable<String>
):
List<Integer> ints = Arrays.asList(0,1,2);
assert Collections.max(ints) == 2;
List<String> strs = Arrays.asList("zero","one","two");
assert Collections.max(strs).equals("zero");
但是我们可能不会选择 T
作为 Number
(因为 Number
不能实现 Comparable
):
List<Number> nums = Arrays.asList(0,1,2,3.14);
assert Collections.max(nums) == 3.14; // 编译报错
正如预期的那样,这里调用 max
是非法的。
这是一个效率提示。 前面的实现使用 foreach
循环来提高简洁性和清晰度。如果效率是一个紧迫的问题,您可能需要重写该方法以使用显式迭代器,如下所示:
public static <T extends Comparable<T>> T max(Collection<T> coll) {
Iterator<T> it = coll.iterator();
T candidate = it.next();
while (it.hasNext()) {
T elt = it.next();
if (candidate.compareTo(elt) < 0) candidate = elt;
}
return candidate;
}
这分配一次迭代器而不是两次,并执行少一次比较。
方法的签名应该尽可能通用以最大化效用。 如果你可以用通配符替换一个类型参数,那么你应该这样做。 我们可以通过替换以下来改进 max
的签名:
<T extends Comparable<T>> T max(Collection<T> coll)
同
<T extends Comparable<? super T>> T max(Collection<? extends T> coll)
遵循获取和放置原则,我们使用 extends
来继承 Collection
,因为我们从集合中获得类型 T
的值,并且我们使用 Comparable
与 super
,因为我们将
类型 T
的值放入 compareTo
方法中。 在下一节中,我们将看到一个不会键入的示例 - 如果上面的 super
子句被忽略。
如果您查看 Java
库中此方法的签名,您将看到一些内容看起来比上面的代码更糟糕:
<T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
这是为了向后兼容,正如我们将在 3.6
节结束时所解释的那样。