Java为使用者提供了两种比较机制:Comparable和Comparator,它们的名字虽然很像,但是实际使用上却并不相同。
总的来说:Comparable是一个排序接口,Comparator是一个比较器接口。接下来会详细介绍二者的区别。
Comparable是一个排序接口,一个类如果Implements了Comparable接口,那么就代表着这个类是“可比较的”或者说“可排序的”。更具体点来说就是,如果你的类实现了这个接口,那么你可以直接调用Collections.sort()方法或者Arrays.sort()方法将一个容器里的类对象排序。(不过这样排序默认是自然排序,即升序排列)
下面我们来看看Java内部的Comparable到底是怎样的:
package java.lang;
import java.util.*;
public interface Comparable {
public int compareTo(T o);
}
从上面的代码可以看出,Comparable是一个接口,并且它使用了泛型。接口内部只有一个方法compareTo。
任何实现Comparable接口的类都必须重写这个compareTo方法。compareTo方法的目的是比较该对象和指定对象o之间的大小。若该对象大于o则返回正整数,小于o则返回负整数,相等则返回0。(由这个返回值的分布你可以猜想到,它的实现方法可以是将两个对象相减,然后返回相减的结果)
以一个Person类为例:
public class Person implements Comparable{
public String name;
public Integer age;
public Person(String name,Integer age){
this.name=name;
this.age=age;
}
@Override
public int compareTo(Person o) {
return this.age-o.age;
}
}
我们希望Person类是可排序的,所以让它实现了Comparable接口,所以在Person类内部重新的compareTo方法,实现方法就是返回二者的age之差。
public class ComparableDemo {
public static void main(String[] args) {
Person p1 = new Person("张三", 18);
Person p2 = new Person("张四", 20);
Person p3 = new Person("张五", 5);
List people = new ArrayList<>();
people.add(p1);people.add(p2);people.add(p3);
System.out.println("排序前:");
people.forEach(person -> System.out.println(person.name + person.age + "岁"));
Collections.sort(people);
System.out.println("排序后:");
people.forEach(person -> System.out.println(person.name + person.age + "岁"));
}
}
运行结果如上图所示,调用Collections.sort()方法后people链表被按照升序排列了。
另外,实现了 Comparable 接口的对象才能够直接被用作 SortedMap (SortedSet) 的 key。
public static void main(String[] args) {
Person p1 = new Person("张三", 18);
Person p2 = new Person("张四", 20);
Person p3 = new Person("张五", 5);
Map map=new TreeMap<>();
map.put(p1,100.0);map.put(p2,70.2);map.put(p3,88.3);
map.forEach((person, aDouble) ->
System.out.println(person.name+person.age+"岁,得分为:"+aDouble));
}
这里的sortedmap用TreeMap来实现。 可以看到结果是按照年龄的升序排列了。
Comparable小结:Comparable接口适用于这个类是可排序的,而且默认为升序。你当然可以在重写compareTo的时候修改比较逻辑,这样可以实现降序排列。但是并不建议这样做,因为当代码量变大之后,我们如果没记住compareTo的结果是反的的话,在其他地方调用compareTo很可能会得到与我们预期相反的结果。
Comparator是比较接口,我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序,这个“比较器”只需要实现Comparator接口即可。也就是说,我们可以通过实现Comparator来新建一个比较器,然后通过这个比较器对类进行排序。该接口定义如下:
package java.util;
public interface Comparator{
int compare(T o1, T o2);
boolean equals(Object obj);
}
可以看到,Comparator也使用了泛型,它的内部用两个方法,一个是compare一个是equals。
注意:在实现Comparator接口的时候,必须要重写compare方法,但是可以不重写equals方法。
compare方法是用来描述比较逻辑的,它与Comparable中的compareTo方法类似。
一般我们都在使用sort函数的时候new一个比较器来描述我们想要的排序逻辑,但是请先记住一点,sort方法一定是按照compare的结果升序排列的。
compare(T o1,T o2)意味着”比较o1与o2"。如果我想把compare定义为:若o1大于o2则返回正整数,若o1小于o2则返回负整数,若o1等于o2则返回0。那么sort函数会按照这个compare的结果升序排列。
public static void main(String[] args) {
Person p1 = new Person("张三", 18);
Person p2 = new Person("张四", 20);
Person p3 = new Person("张五", 5);
List people = new ArrayList<>();
System.out.println("排序前:");
people.add(p1);people.add(p2);people.add(p3);
people.forEach(person -> System.out.println(person.name + person.age + "岁"));
people.sort(new Comparator() {
@Override
public int compare(Person o1, Person o2) {
if(o1.age>o2.age) return 1;
if(o1.age System.out.println(person.name+person.age+"岁"));
}
可以看到按照上面的排序逻辑,调用sort方法排序,结果是升序排列的。(这和Comparable相同)
但是我们如果将comparator的比较逻辑反过来:
people.sort(new Comparator() {
@Override
public int compare(Person o1, Person o2) {
if(o1.age>o2.age) return -1;
if(o1.age
可以看到,反过来之后就变成了降序排列。所以,当我们在不同场景下需要不同的排序逻辑时,我们只需要修改Comparator的排序逻辑即可。
这里也可以总结一点关于写Comparator排序逻辑的经验方法:当我们希望按照某个属性自然排序的时候,那么就按照正常比大小的结果返回正负整数以及0即可。如果想要按照某个属性降序排列的时候,我们可以这样看:o1要排在o2前面,所以返回负整数,而我们希望的是降序,那么必然o1大于o2,所以在if语句里填上o1>o2的条件即可。
Java 中的两种排序方式:
对于一些普通的数据类型(比如 String, Integer, Double…),它们默认实现了Comparable 接口,实现了 compareTo 方法,我们可以直接使用。
而对于一些自定义类,如果它的比较逻辑比较简单,并且排序不需要更改逻辑,那么可以实现Comparable。但是当我们的排序逻辑很复杂而且可能会变动的时候,更方便的方法就是创建 Comparator
两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。 用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。