一个 List l 可能被做如下排序:
Collections.sort(l);
假如这个 list 由 String 元素所组成, 那么它将按词典排序法(按字母顺序)进行排序; 假如它是由 Date 元素所组成, 那么它将按年代顺序来排序。 Java 怎么会知道该怎么做呢? 这一定是个魔术! 其实不然。实际上, String 和 Date 均实现了Comparable接口。 Comparable 接口为一个类提供一个 自然排序( natural ordering), 它答应那个类的对象被自动排序。下表列出了实现了Comparable 的JDK类:
类 自然排序
Byte 带符号的数字排序
Character 不带符号的数字排序
Long 带符号的数字排序
Integer 带符号的数字排序
Short 带符号的数字排序
Double 带符号的数字排序
Float 带符号的数字排序
BigInteger 带符号的数字排序
BigDecimal 带符号的数字排序
File 依靠系统的按路径名字母顺序排序
String 按字母顺序排序
Date 按年代顺序排序
CollationKey 特定字符集按字母顺序排序
假如你要为一个其元素没有实现 Comparable的列表排序,Collections.sort(list) 将扔出一个 ClassCastException。类似的,假如你要为一个其元素没有作相互比较的列表进行排序, Collections.sort 将扔出一个 ClassCastException. 能够被相互比较的元素被称作 mutually comparable(可相互比较的)。 虽然不同类型的元素有可能被相互比较,但以上列出的任何JDK类型都不答应在类之间的比较 (inter-class comparison)。
假如你只是要为可比较的元素的列表进行排序,或为它们创建排序的对象集, 则这就是你实际需要了解的全部有关 Comparable 接口的内容。假如你要实现你自己的 Comparable 类型,则下一节将会引起你的爱好。
编写你自己的 Comparable 类型
Comparable 接口由一个单一的方法构成:
public interface Comparable {
public int compareTo(Object o);
}
compareTo 方法将接收对象与特定对象进行比较,并在接收对象小于、等于或大于特定对象时分别返回负整数、空或一个正整数。假如特定对象不能与接收对象相比较,该方法扔出一个ClassCastException. 这是一个表示某人姓名的类(a class representing a person´s name), 它实现了 Comparable:
import java.util.*;
public class Name implements Comparable {
private String firstName, lastName;
public Name(String firstName, String lastName) {if (firstName==null lastName==null)
throw new NullPointerException();
this.firstName = firstName;
this.lastName = lastName;
}
public String firstName() {return firstName;}
public String lastName() {return lastName;}
public boolean equals(Object o) {
if (!(o instanceof Name))
return false;
Name n = (Name)o;return n.firstName.equals(firstName) &&n.lastName.equals(lastName);
}
public int hashCode() {
return 31*firstName.hashCode() + lastName.hashCode();
}
public String toString() {return firstName + " " + lastName;}
public int compareTo(Object o) {Name n = (Name)o;
int lastCmp = lastName.compareTo(n.lastName);
return (lastCmp!=0 ? lastCmp :
firstName.compareTo(n.firstName));
}}
为了使这个例子短一些,该类受到了一点限制:它不支持中间名,它要求必须同时具有first name 和 last name, 而这不是在全世界都通用的。尽管如此,这个例子仍有几个重要之处:
Name 对象是不变的( immutable)。作为相等、不变类型的所有其它事情就是如何做的问题,非凡是对那些将被用来作为 Sets 中的元素或 Maps 中的键的对象来说,更是如此。假如你对这些 对象集 中的元素或键做了更改,这些 对象集 将中断。
构造函数可检查它的参数是否为 null。 这可以保证所有的Name 对象都能很好地形成。因而没有其它方法会扔出NullPointerException.
hashCode 方法被重新定义。对重新定义 equals 方法的任意类来说,这是必需的(essential)。 一般约定(general contract)需要 Object.equals. (Equal 对象必须具有相等的哈希代码) 。
假如特定对象为 null,或一个不适当的类型, equals 方法则返回 false。 在这种情况下, compareTo 方法扔出一个运行时异常。这两个行为都是各自方法的一般约定所必需的。
toString 方法已被重新定义,从而可以以人们能够读懂的形式打印 Name 。这总是一个好主意,非凡是对要被放入对象集 中的对象来说,更有益处。各种 对象集 类型的 toString 方法依靠它们的元素、键和值的 toString 方法。
由于这一节介绍的是有关元素排序的问题,因而让我们稍微多谈一点 Name 的 compareTo 方法。它实现标准的姓名-排序算法,在该算法中,last name 优先于 first name。这恰恰是你在一个natural ordering(自然排序)中所想要的。 假如自然排序不自然,那才轻易引起混乱呢!
请看 compareTo 是如何被实现的,因为它是相当典型的。首先,你将 Object 参数转换为适当类型; 假如参数类型是不适当的,则会扔出一个适当的异常(ClassCastException);那么你应该比较对象的最重要部分(在此案例中为 last name)。通常,你可以使用该部分的类型的自然排序。 在次案例中,该部分是一个 String, 并且自然的(按词典顺序的)排序正是所要求的。假如比较的结果是空(它表示等同性)之外的其它东西,你就做完了:你可以返回结果。 假如最重要的部分是相等的,你就应该继续比较次重要部分。在此案例中,只有两个部分 (first name and last name)。 假如有更多的部分,你就应该以显而易见的方式继续进行,直到发现两个不相等的部分(否则你就应该比较最不重要的部分),这时,你就可以返回比较结果了。 这是 一个建立 Name 对象列表并对它们进行排序的小程序: