在项目开发中,我们经常要对一组数据进行排序,或者升序或者降序,在Java中排序有多种方式,最土的方式就是自己写排序算法,比如冒泡排序、快速排序、二叉树排序等,但一般不需要自己写,JDK 已经为我们提供了很多的排序算法,我们直接拿来用就成了。
1. 基本类型数组排序
对于基本类型数组进行排序,一般我们直接使用 Arrays 类提供的 sort() 方法进行排序就可以了,看源码可以发现,sort() 方法在算法上已经进行了很好的优化,如果没有特殊的需求,该方法完全可以满足我们的需要。案例代码如下:
public static void main(String[] args) { // 数组初始化 int[] a = { 3, 5, 2, 1, 8, 6, 9, 4, 0}; // 排序 Arrays.sort(a); // 输出排序之后的结果 for (int i = 0; i < a.length; i++) { System.out.println(a[i]); } }2. 对象数组排序
对于对象数组进行排序,在Java中,有两种实现方式,一种是排序的对象实现 Comparable 接口,然后调用 Arrays 类的sort(Object[] a) 方法进行排序;另一种是创建一个实现了 Comparator 接口的单独类,然后调用 Arrays 类的 sort(T[] a, Comparator<? super T> c) 方法。下面看两种方法的具体实现代码:
实现了 Comparable 接口的Person类:
public class Person implements Comparable<Person> { private int id; private String name; private int age; public Person(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } /* id、name、position的getter/setter方法省略 */ @Override public int compareTo(Person o) { // TODO 添加实现代码 } @Override public String toString() { return "Person [id=" + id + ", name=" + name + ", age=" + age + "]"; } }为什么实现该接口就可以实现 Person 类的排序呢?在后面在做介绍。现在如果想让 Person 类按 id 从小到大进行排序,compareTo 实现类似如下代码:
@Override public int compareTo(Person o) { return id - o.id; }如果想让 Person 类按 id 的大小逆向进行排序,只要将 compareTo 实现改为 return o.id - id; 即可。
但是如果我们想让 Person 类按 id 从小到大进行排序,然后再按 age 从小到大进行排序(假设这里的 id 可以重复),compareTo 实现代码如下:
@Override public int compareTo(Person o) { return id - o.id != 0 ? (id - o.id) : (age - o.age); }可以看到 compareTo 实现都是自己手动编写的,如果以后排序的元素多了,自己实现起来会比较麻烦。这里我们可以通过使用 Apache 的工具类来简化处理,首先需要下载 commons-lang-*.jar,之后将该包添加到项目中(通过Build Path设置),并在 compareTo() 方法实现中调用相关类即可,现在同样让 Person 类按 id 排序,compareTo 实现代码变得如下所示:
@Override public int compareTo(Person p) { return new CompareToBuilder() .append(id, p.id).toComparison(); }
commons-lang-*.jar下载地址:http://commons.apache.org/lang/
CompareToBuilder 类 append() 方法如何实现比较的呢?源码如下(对 int 类型的处理):
private int comparison;
public CompareToBuilder() {
super();
comparison = 0;
}
public CompareToBuilder append(int lhs, int rhs) {
if (comparison != 0) {
return this;
}
comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
return this;
}
comparison 初始化为 0 ,当comparison 不等于 0 的时候,即找到不相等的地方时,返回当前对象引用;如果 comparison 等于 0 ,说明还没找到不相同的地方,就需要比较当前传入的参数,并将结果赋值给 comparison 变量,然后返回当前对象引用。由于 append() 方法返回 CompareToBuilder 类本身,所以可以在调用 append() 方法之后可以无限调用 append() 方法,所以如果想要添加新的比较项,只要再追加一个 append() 方法即可,实现方法很简单,却很精辟。
基于上面的分析和对对代码的理解,不难实现 Person 类先按 id 排序,id 相同再按 age 排序,代码如下:
@Override public int compareTo(Person p) { return new CompareToBuilder() .append(id, p.id) .append(age, p.age).toComparison(); }
对于逆序排序,那就更简单了,将 id 和 p.id 换个位置就行了。
排序对象写好了,那么 就开始写测试类,代码如下:
public static void main(String[] args) { Person[] persons = { new Person(2, "张三", 25), new Person(1, "赵七", 23), new Person(1, "王五", 21), new Person(2, "李四", 24), new Person(3, "马六", 22) }; Arrays.sort(persons); for(Person person : persons) { System.out.println(person.toString()); } }
结果如下:
Person [id=1, name=王五, age=21] Person [id=1, name=赵七, age=23] Person [id=2, name=李四, age=24] Person [id=2, name=张三, age=25] Person [id=3, name=马六, age=22]
结果可以看出,Person 类先按 id 排序,再按 age 进行排序的。那么 Arrays 类 sort(Object[] a) 方法是如何进行排序的呢?Arrays 类部分源码如下:
public static void sort(Object[] a) { Object[] aux = (Object[])a.clone(); mergeSort(aux, a, 0, a.length, 0); } private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off) { int length = high - low; // 对最小的数组进行排序(INSERTIONSORT_THRESHOLD=7) if (length < INSERTIONSORT_THRESHOLD) { for (int i=low; i<high; i++) for (int j=i; j>low && ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--) swap(dest, j, j-1); return; } /* 其他排序代码省略 */ }
在 mergeSort() 方法中我们可以看到该方法其实是调用排序对象的 compareTo() 方法,而在该方法的其他处理中也是通过调用 compareTo() 方法对排序对象进行比较的(有兴趣的可以看看源码)。如果使用上面的方式对对象数组进行排序,但被排序的对象并没有实现 comparable 接口,那么在调用 sort() 方法时会抛出 ClassCastException 异常。
上面案例虽然实现对对象数组的排序,但是这种方式的实现会使得对于某个对象的数组排序只有唯一的一种排序方式,而在项目中我们可能需要在不同的地方使用不同的排序方式,显然上面的实现方式不能满足我们的要求了,这时候我们可以通过创建一个实现了 Comparator 接口的单独类来帮助我们实现(第二种实现方式),创建类的代码如下:
public class PersonComprator implements Comparator<Person> { @Override public int compare(Person o1, Person o2) { return new CompareToBuilder() .append(o1.getId(), o2.getId()) .append(o1.getAge(), o2.getAge()).toComparison(); } }
该类实现了 Comparator 接口,并通过泛型限制了排序的对象,需要注意的是这里并不是 compareTo() 方法,而是 compare() 方法,还有就在实现上有细微的差别。一样看测试类代码:
public static void main(String[] args) { Person[] persons = { new Person(2, "张三", 25), new Person(1, "赵七", 23), new Person(1, "王五", 21), new Person(2, "李四", 24), new Person(3, "马六", 22) }; Arrays.sort(persons, new PersonComprator()); for(Person person : persons) { System.out.println(person.toString()); } }
和之前测试类的区别就是调用 Arrays 类的 sort(T[] a, Comparator<? super T> c) 方法代替了 sort(Object[]) 方法。结果和之前一样,就不在列出来了。
同样我们通过源码看看该方法是如何实现排序的,源码如下:
public static <T> void sort(T[] a, Comparator<? super T> c) { T[] aux = (T[])a.clone(); if (c==null) mergeSort(aux, a, 0, a.length, 0); else mergeSort(aux, a, 0, a.length, 0, c); } private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off, Comparator c) { int length = high - low; // 对最小的数组进行排序(INSERTIONSORT_THRESHOLD=7) if (length < INSERTIONSORT_THRESHOLD) { for (int i=low; i low && c.compare(dest[j-1], dest[j])>0; j--) swap(dest, j, j-1); return; } /* 其他排序代码省略 */ }
方法开头对被排序的对象数组进行了克隆,判断比较器是否为空,如果为空的话,则使用第一种排序方式进行排序。不为空的情况,则调用含有比较器的排序方法,而该方法中正是调用了比较器的 compare() 方法来比较对象的大小,从而实现对象数组排序,到这里实现原理基本也就清楚了。
3. 对象列表排序
对象累不排序基本上和对象数组排序相同,都可以通过实现 Comparable 接口或新建一个实现了 Comparator 接口的单独类来实现。不同的是排序使用到的类不再是 Arrays 类,而是 Collections 类,该类在 java.util 包中,测试类代码如下:
public static void main(String[] args) { // 列表初始化 List<Person> persons = new ArrayList<Person>(); persons.add(new Person(2, "张三", 25)); persons.add(new Person(1, "赵七", 23)); persons.add(new Person(1, "王五", 21)); persons.add(new Person(2, "李四", 24)); persons.add(new Person(3, "马六", 22)); // 排序 Collections.sort(persons); // 输出排序结果 for(Person person : persons) { System.out.println(person.toString()); } }
对于列表实现倒序排序,有额外两种解决方式:第一种是直接使用 Collections.reverse(List<?> list) 方法实现,将上面排序代码改为 Collections.reverse(persons); 第二种是通过 Collections.sort(list, Collections.reverseOrder(new PositionComparator()) 实现,排序代码改为 Collections.sort(persons, new PersonCompator())。
总结:Comparable 接口可以作为实现类的默认排序法,Comparator 接口则是一个类的扩展排序工具。