前几天在项目中遇到了一个将复杂对象进行排序的问题:计算BingMap地图上距离当前位置5KM内发生事故(TrafficIncident)的点到当前位置的距离,并按距离升序排序。距离都算出来了,但这些TrafficIncident对象的排序却难到了我。经同事提醒,Comparable或Comparator是一个不错的选择。于是在网上搜索了一些资料,总结下来。
方式一: 实现Comparable接口
Comparable是java.lang包下的一个接口,该接口里只有一个compareTo()方法:
package java.lang; import java.util.*; public interface Comparable<T> { public int compareTo(T o); }
Comparable翻译为“可比较的”,表明实现该接口的类都是可以比较的,即实现Comparable接口的类本身就已经支持自比较,例如: String、Integer 自己就可以完成比较大小操作,它们已经实现了Comparable接口。查看String类的源码可以看见是这样声明的(JDK1.7):
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
而Integer类的声明如下:
public final class Integer extends Number implements Comparable<Integer>
可见它们都实现了Comparable接口,它们的实例都可以通过调用自身的compareTo()方法来比较自己,例如:
String s1 = "aa"; String s2 = "ab"; System.out.println(s1.compareTo(s2)); //返回是两字符串第一个不同的char的unicode编码的差 Integer i1 = 3; Integer i2 = 2; System.out.println(i1.compareTo(i2)); //前者大返回1,后者大返回-1,相等返回0
而String类中的compareTo方法时这样实现的:
public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2;//返回第一个不同字母的unicode的差 } k++; } return len1 - len2;//如果两个字符串只有长度不同,则返回长度的差 }
Integer类中的compareTo()方法的实现如下:
public int compareTo(Integer anotherInteger) { return compare(this.value, anotherInteger.value); } public static int compare(int x, int y) { return (x < y) ? -1 : ((x == y) ? 0 : 1); //前者大返回1,后者大返回-1,相等返回0 }
因此,我们可以参照String类及Integer类对于Comparable接口的实现来实现自己的比较方式,然后调用compareTo方法即可知道他们的顺序。 下面是一个例子(按Person类的年龄排序):
import java.util.Arrays; public class ComparableTest { public static void main(String[] args) { Person[] persons = new Person[]{ new Person(20, "P1"), new Person(60, "P2"), new Person(50, "P3"), new Person(40, "P4") }; Arrays.sort(persons); System.out.println(); //下面代码的结果一样 //List<Person> personList = Arrays.asList(persons); //Collections.sort(personList); //System.out.println(personList); } } class Person implements Comparable<Person> { private int age; private String name; public Person(int age, String name) { this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } //实现Comparable接口的compareTo方法 @Override public int compareTo(Person o) { return this.age - o.age; } @Override public String toString() { return "Person[name=" + name + ", age=" + age + "]"; } }
执行结果是:
[Person[name=P1, age=20], Person[name=P4, age=40], Person[name=P3, age=50], Person[name=P2, age=60]]
由此可见,Person对象已经通过Arrays.sort()方法按照age排序。
方式二: 实现Comparator接口
Comparator是java.util包下的一个接口,该接口里有两个方法compare()和equals():
package java.util; public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }
Comparator翻译为“比较器”,表明实现该接口的类都是一个比较器,一般在要比较的对象类型不支持自比较或者自比较函数不能满足要求时使用。使用此接口时,我们可以不在需要比较的类型(这里是Person类)中实现比较过程,而是把这个过程转移到了Comparator接口的compare方法中。于是,上面的例子需要修改为:
import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; public class ComparableTest { public static void main(String[] args) { Person[] persons = new Person[]{ new Person(20, "P1"), new Person(60, "P2"), new Person(50, "P3"), new Person(40, "P4") }; //Arrays.sort方法不支持使用Comparator比较器了,这里只能使用Collections.sort来排序 List<Person> personList = Arrays.asList(persons); System.out.println("Before sort: \r\n" + personList); //这里,将一个比较器(Comparator)传递给sort方法作为参数,按照里面的比较逻辑对Person进行排序 Collections.sort(personList, new PersonComparator()); System.out.println("After sort: \r\n" + personList); } } //被比较的类型不需要实现任何接口 class Person { private int age; private String name; public Person(int age, String name) { this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person[name=" + name + ", age=" + age + "]"; } } //这是一个比较器,用于比较Person对象 class PersonComparator implements Comparator<Person> { @Override public int compare(Person o1, Person o2) { //两个Person对象的比较过程,当然,这里可以实现更多更复杂的比较过程 return o1.getAge() - o2.getAge(); //如果o1.age > o2.age,方法返回正数,为正数正是表明哦o1 > o2 //如果o1.age = o2.age,方法返回0,返回0正是表明o1 == o1 //如果o1.age < o2.age,方法返回正数,为负数正是表明哦o1 < o2 } }
输出结果为:
Before sort: [Person[name=P1, age=20], Person[name=P2, age=60], Person[name=P3, age=50], Person[name=P4, age=40]] After sort: [Person[name=P1, age=20], Person[name=P4, age=40], Person[name=P3, age=50], Person[name=P2, age=60]]
由此可见,我们可以将一个自定义的比较器作为参数传递给Collections.sort()方法,下图是API中的Collections.sort()方法:
Person对象已经按照比较器中的规则进行排序。如果在使用Collections.sort()方法时不提供这个Comparator(其实就是使用前文介绍的排序方式),那么就以自然顺序排序,如Collections.sort()方法的API所说:Sorts the specified list into ascending order, according to the natural ordering of its elements. All elements in the list must implement the Comparable interface. 这里的自然顺序就是实现Comparable接口设定的排序方式。
细心的同学已经看到,Comparator接口中还有个equals()方法没有实现,可程序并没有报错,原因是实现该接口的类也是Object类的子类,而Object类已经实现了equals方法。
相同点
都是用于比较两个对象“顺序”的接口
都可以使用Collections.sort()方法来对对象集合进行排序
不同点
Comparable位于java.lang包下,而Comparator则位于java.util包下
Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序
总结
使用Comparable接口来实现对象之间的比较时,可以使这个类型(设为A)实现Comparable接口,并可以使用Collections.sort()方法来对A类型的List进行排序,之后可以通过a1.comparaTo(a2)来比较两个对象;
当使用Comparator接口来实现对象之间的比较时,只需要创建一个实现Comparator接口的比较器(设为AComparator),并将其传给Collections.sort()方法即可对A类型的List进行排序,之后也可以通过调用比较器AComparator.compare(a1, a2)来比较两个对象。
可以说一个是自己完成比较,一个是外部程序实现比较的差别而已。
用 Comparator 是策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。
比如:你想对整数采用绝对值大小来排序,Integer 是不符合要求的,你不需要去修改 Integer 类(实际上你也不能这么做)去改变它的排序行为,这时候只要(也只有)使用一个实现了 Comparator 接口的对象来实现控制它的排序就行了。
两种方式,各有各的特点:使用Comparable方式比较时,我们将比较的规则写入了比较的类型中,其特点是高内聚。但如果哪天这个规则需要修改,那么我们必须修改这个类型的源代码。如果使用Comparator方式比较,那么我们不需要修改比较的类,其特点是易维护,但需要自定义一个比较器,后续比较规则的修改,仅仅是改这个比较器中的代码即可。