一个List可以如下排序:
Collections.sort(l);
如果List类型是String,将按字母顺序排序,如果是Date,将按时间排序。
因为String和Date都实现了 Comparable接口。Comparable的实现为类提供了自然排序,这允许自动排序该类的对象。下表总结了实现Comparable的一些重要的Java平台实现类。
Class | Natural Ordering |
---|---|
Byte | Signed numerical |
Character | Unsigned numerical |
Long | Signed numerical |
Integer | Signed numerical |
Short | Signed numerical |
Double | Signed numerical |
Float | Signed numerical |
BigInteger | Signed numerical |
BigDecimal | Signed numerical |
Boolean | Boolean.FALSE < Boolean.TRUE |
File | System-dependent lexicographic on path name |
String | Lexicographic |
Date | Chronological |
CollationKey | Locale-specific lexicographic |
如果list没有实现Comparable接口,Collections.sort(list)会抛出 ClassCastException
异常。
如果试图对一个不能使用comparator进行元素比较的列表进行排序,Collections.sort(list, comparator) 也会抛出ClassCastException。
自定义Comparable类型
Comparable接口:
public interface Comparable {
public int compareTo(T o);
}
一个例子:
public class Name implements Comparable {
private final 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(Name n) {
int lastCmp = lastName.compareTo(n.lastName);
return (lastCmp != 0 ? lastCmp : firstName.compareTo(n.firstName));
}
}
为了保持前面示例的简短,该类有一定的限制:它不支持中间名,它要求有姓和名,并且它没有以任何方式国际化。
尽管如此,它阐明了以下要点:
- Name对象是immutable不可变的。在其他条件相同的情况下,不可变类型是可行的,特别是在Set中用作元素或在Map中用作键的对象。如果在集合中修改它们的元素或键,这些集合将被破坏。
- 构造函数检查它的参数是否为null,为null是抛出NullPointerException。
- 重新定义了hashCode方法。这对于任何重定义equals方法的类都是必不可少的。(相等对象必须具有相等的哈希码。)
- 如果指定的对象为null或类型不合适,则equals方法返回false。compare方法在这些情况下抛出运行时异常。这两种行为都是各自方法的一般契约所要求的。
- 重定义toString方法。
compareTo的实现先比较lastName,如果lastName相等在比较firstName。
创建并排序一组Name:
public class NameSort {
public static void main(String[] args) {
Name nameArray[] = {
new Name("John", "Smith"),
new Name("Karl", "Ng"),
new Name("Jeff", "Smith"),
new Name("Tom", "Rich")
};
List names = Arrays.asList(nameArray);
Collections.sort(names);
System.out.println(names);
}
}
Comparators
如果以特定规则排序而不是自然顺序或排序一些对象但又不想实现Comparable接口,只需要提供一个comparator:
public interface Comparator {
int compare(T o1, T o2);
}
假设有一个Employee对象,假设其自然排序是之前定义在Name中的顺序:
public class Employee implements Comparable {
public Name name() { ... }
public int number() { ... }
public Date hireDate() { ... }
...
}
现在要按级别排序:
import java.util.*;
public class EmpSort {
static final Comparator SENIORITY_ORDER =
new Comparator() {
public int compare(Employee e1, Employee e2) {
return e2.hireDate().compareTo(e1.hireDate());
}
};
// Employee database
static final Collection employees = ... ;
public static void main(String[] args) {
List e = new ArrayList(employees);
Collections.sort(e, SENIORITY_ORDER);
System.out.println(e);
}
}
Comparator无法用在有序集合上,比如TreeSet ,如果使用这个Comparator将多个同一天雇佣的employee插入到TreeSet,只要第一个雇员添加成功;第二个将被视为重复的元素并被忽略。
要解决这个问题,只需调整Comparator,使其生成一个与equals兼容的排序。换句话说,调整它,使在使用compare时被视为相等的元素是那些在使用equals进行比较时也被视为相等的元素。
static final Comparator SENIORITY_ORDER =
new Comparator() {
public int compare(Employee e1, Employee e2) {
int dateCmp = e2.hireDate().compareTo(e1.hireDate());
if (dateCmp != 0)
return dateCmp;
return (e1.number() < e2.number() ? -1 :
(e1.number() == e2.number() ? 0 : 1));
}
};
你可能想把最后一句换成:
return e1.number() - e2.number();
不要这样做,除非你绝对确定没有人会有一个负的员工号码!这个技巧通常不起作用,因为有符号整数类型不足以表示两个任意有符号整数的差值。如果i是一个较大的正整数,而j是一个较大的负整数,i - j将溢出并返回一个负整数。由此产生的Comparator违反了我们一直在讨论的四个技术限制之一(传递性),并产生了可怕的、微妙的bug。这不是一个纯粹的理论问题。