public final class String extends implements , <>,
如果你尝试为没有实现Comparable接口的元素排序,编译器会出错:The generic method sort(List<T>) of type Collections is not applicable for the arguments(ArrayList<你刚编写的类>). The inferred type 你刚编写的类is not a valid substitute for the bounded parameter <T extends Comparable<? super T>>。
此接口强行对实现它的每个类的对象进行整体排序。此排序被称为该类的自然排序,类的 compareTo 方法被称为它的自然比较方法。实现此接口的对象列表(和数组)可以通过Collections.sort(和 Arrays.sort)进行自动排序。实现此接口的对象可以用作有序映射表中的键或有序集合中的元素,无需指定比较器。对于类C的每一个 e1 和 e2 来说,当且仅当 (e1.compareTo((Object)e2) == 0) 与 e1.equals((Object)e2) 具有相同的布尔值时,类 C 的自然排序才叫做与 equals 一致。注意,null 不是任何类的实例,即使 e.equals(null) 返回 false,e.compareTo(null) 也会抛出 NullPointerException。强烈推荐(虽然不是必需的)使自然排序与 equals 一致。这是因为在使用其自然排序与 equals 不一致的元素(或键)时,没有显式比较器的有序集合(和有序映射表)行为表现“怪异”。尤其是,这样的有序集合(或有序映射表)违背了根据 equals 方法定义的集合(或映射表)的常规协定。例如,如果将两个键 a 和 b 添加到一个没有使用显式比较器的有序集合中,使得 (!a.equals((Object)b) && a.compareTo((Object)b) == 0),则第二个 add 操作返回 false(有序集合的大小没有增加),因为从有序集合的角度来看,a 和 b 是等效的。 实现了comparable接口的类,其对象可以作为SortedMap的key,或者SortedSet的元素。
我们只能在类中实现compareTo()一次,不可能说经常来修改类的代码实现自己想要的排序,因此如果要以不同于compareTo()方法中指定的顺序排序我们的类对象,那么该怎么办呢?大家一定注意到Collections.sort()有一个重载版本。 public static <T> void sort(List<T> list, Comparator<? super T> c)根据指定比较器产生的顺序对指定列表进行排序。此列表内的所有元素都必须可使用指定比较器相互比较(也就是说,对于列表中的任意 e1 和 e2 元素,c.compare(e1, e2) 不得抛出 ClassCastException)。此排序被保证是稳定的:不会因调用 sort 而对相等的元素进行重新排序。 Comparator<? super T> c 第二个参数是实现了接口Comparator的实例对象。注意这两者的区别,Comparable 接口的方法:public int compareTo(T o),绝不会是Object; 只有一个参数,而Comparator接口却有两个参数:int compare(T o1, T o2);
Comparator 可以通过 Collections类的reverseOrder()这个方法强势返回对Comparable排序的倒转,如:Arrays.sort(a, Collections.reverseOrder()); 如果是String,就是按照逆字典顺序进行排序。
这两个接口和集合类本身无关,但通常和集合内的元素有关,因为集合的排序要用到它们中的方法。一个类的实例要想实现排序,必须实现Comparable,或者提供相应的Comparator。说清楚这两者的来历与大概的作用,以及共同点,那么再说下他们两者的区别。只是Comparable(可比较的)是在集合内部定义的方法实现的排序,比如,两个人要比较身高,分辨高矮是人类固有的能力,两个人只要站到一起就能分出谁高谁矮。Comparator(比较器)是在集合外部实现的排序。所以,如想实现排序,就需要在集合外定义Comparator接口的方法compare()或在集合内实现Comparable接口的方法compareTo(),Comparable是一个对象本身就已经支持自比较所需要实现的接口(如String Integer自己就可以完成比较大小操作)而Comparator是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较。还有个地方就是,实现Comparable只能定义一种比较方法,但是有时候会对一个集合进行不同的排序方法,此时就可以提供别各种各样的Comparator来对集合排序,而对于要排序的元素不需要更改,所以我觉得Comparator提供了更多的灵活性。使用这种策略来比较时,如何进行比较和两个对象本身无关,而是由第三者(即比较器)来完成的。只要实现Comparator接口,任何一个对象都可能成为一个“比较器”,但比较器并不是比较自己的实例,而是比较另外两个对象,比较器在这里充当“仲裁者”的角色,这也就是为什么compare()方法需要两个参数。比如,两个人要比较谁智商更高,靠他们自身无法进行,这时要借助一个比较器(比如,智商测试题)。那么先看一个非常简单易懂的示例:
import java.util.*;
public class Pockets {
public static void main(String[] args) {
String[] sa = {"nickel", "button", "key", "lint"};
Sorter s = new Sorter(); //新建一个排序器
for(String s2: sa){ System.out.print(s2 + " "); }
Arrays.sort(sa, s); //这个排序器s是与sa这个类对象是相关。
System.out.println();
for(String s2:sa){ System.out.print(s2+" "); }
}
static class Sorter implements Comparator<String>{ //注意红色的部分就是你要去排序的类,如果你文不对题,硬让这个类去排序一个未知的class Dog的类,会报错:The method sort(T[], Comparator<? super T>) in the type Arrays is not applicable for the arguments (UU[], Pockets.Sorter)
public int compare(String a, String b){
return a.compareTo(b);
}
}
}
再在更加复杂的概念上理解这个排序机制,在网上找到一个例子,维护一个简单的员工数据库,每个员工是一个Employee类的实例。Employee类可定义为:
public class Employee {
private String num;
private String name;
private int age;
private int salary;
public Employee(String num, String name) {
this.num = num;
this.name = name;
}
public void setName(String newNum) {
num = newNum;
}
public void setAge(int newAge) {
age = newAge;
}
public void setSalary(int newSalary) {
salary = newSalary;
}
public String getNum() {
return num;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getSalary() {
return salary;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("Employee Information:");
sb.append("\n");
sb.append("Number:");
sb.append(getNum());
sb.append("\n");
sb.append("Name:");
sb.append(getName());
sb.append("\n");
sb.append("Age:");
sb.append(getAge());
sb.append("\n");
sb.append("Salary:");
sb.append(getSalary());
sb.append("\n");
return sb.toString();
}
}
EmployeeDatabase类创建Employee类的实例,并把它们存入集合:
Java code
import java.util.*;
public class EmployeeDatabase {
public static void main(String[] args) {
List<Employee> allEmployees = new ArrayList<Employee>();
Employee employee1 = new Employee("AAA", "Barack Omaba");
employee1.setAge(50);
employee1.setSalary(9999);
allEmployees.add(employee1);
Employee employee2 = new Employee("BBB", "George Bush");
employee2.setAge(60);
employee2.setSalary(5999);
allEmployees.add(employee2);
System.out.println(allEmployees);
}
}
现在,你需要检索所有员工,并让他们按一定顺序显示(比如按年龄递增),这时需要用到Collections.sort()方法。Collections.sort()有两种策略:一种是让集合元素本身实现Comparable接口,另一种是使用用户提供的比较器(即Comparator)。使用第一种策略时,必须修改元素类的定义,让它实现Comparable,因此,你必须把Employee类修改为:
public class Employee implements Comparable<Employee> {
public int compareTo(Employee another) {
return getAge() - another.getAge();
}
// 其余部分不变
}
说明一下,因为compareTo()方法约定:本对象大于另一个对象时,返回大于0的整数,小于时返回小于0的整数,等于时返回0。所以,可以直接返回两者年龄的差,来实现按年龄比较。这样就可以在main()方法中使用Collections.sort(allEmployees);来对员工按年龄排序了。但是,这种排序是非常不灵活的:第一,需要修改集合元素类Employee,而很多情况下,我们没有办法修改公共的类。第二,没有办法实现多种方式排序,如按编号,按姓名,按薪水等等。这时需要使用另一种策略,即Comparator。Comparator使用其compare()方法返回的整数来比较两个对象,规则和compareTo()一样。如同样实现年龄比较,使用Comparator时,无需修改Employee类,可以在排序的时候定义相应的比较器。使用Collections.sort()方法的另一个版本:
Collections.sort(allEmployees, new Comparator<Employee>() {
public int compare(Employee one, Employee another) {
return one.getAge() - another.getAge();
}
});
这里使用了匿名内部类,实际上相当于先定义一个比较器类,如:
class EmployeeComparator implements Comparator<Employee> {
public int compare(Employee one, Employee another) {
return one.getAge() - another.getAge();
}
}
再使用:
Collections.sort(allEmployees, new EmployeeComparator());
可以看到,比较器完全独立于元素类Employee,因此可以非常方便地修改排序规则。你还可以定义一系列比较器,供排序时选择使用,如:
// 按薪水升序
class EmployeeSalaryAscendingComparator implements Comparator<Employee> {
public int compare(Employee one, Employee another) {
return one.getSalary() - another.getSalary();
}
}
// 按薪水降序
class EmployeeSalaryDescendingComparator implements Comparator<Employee> {
public int compare(Employee one, Employee another) {
return another.getSalary() - one.getSalary();
}
}
相应的使用方法如:
Collections.sort(allEmployees, new EmployeeSalaryAscendingComparator());
Collections.sort(allEmployees, new EmployeeSalaryDescendingComparator());
使用Comparator时,元素类无需实现Comparable,因此我们保持最初版本的Employee,但实际应用中,可以用Comparable的compareTo()方法来定义默认排序方式,用Comparator定义其他排序方式。实现其compare(T o1,T o2)有一个约定,就是 a.compare(b) == 0 和a.equals(b)要有相同的boolean结果。