Skip to content

Latest commit

 

History

History
109 lines (67 loc) · 4.24 KB

02_Maximum_of_a_Collection.md

File metadata and controls

109 lines (67 loc) · 4.24 KB

在本节中,我们将展示如何使用 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,并且我们说 TComparable <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)

遵循 GetPut 原则,我们使用 extends 来继承 Collection,因为我们从集合中获得类型 T 的值,并且我们使用 Comparablesuper,因为 我们将类型 T 的值放入 compareTo 方法中。 在下一节中,我们将看到一个不会键入的示例 - 如果上面的 super 子句被忽略。

如果您查看Java库中此方法的签名,您将看到一些内容看起来比上面的代码更糟糕:

   <T extends Object & Comparable<? super T>>
   T max(Collection<? extends T> coll)

这是为了向后兼容,正如我们将在3.6节结束时所解释的那样。