深入理解Comparable和Comparator
Comparable和Comparator区别
JDK version:1.8.0_171
本文先介绍Comparator和Comparable两个接口的作用并给出源码分析,最后分别给出实际案例
1. Comparable和Comparator简单介绍
-
Comparable
实现Comparable接口的类都支持自然排序(文档中称为natural ordering),而接口中的compareTo方法则是说明了默认排序的实现(即可以通过重写该方法来定制新的排序方式)。
实现了Comparable接口的Lists(或arrays)对象可以直接通过Collections.sort(或Arrays.sort)进行排序。实现了该接口的对象能够充当sorted map中的键或者sorted set中的元素,而不需要指定comparator。
package java.lang;
import java.util.*;
public interface Comparable<T> {
public int compareTo(T o);
}
Comparable接口中只存在一个compareTo方法,该方法传入一个泛型对象,标明所要比较的另一个对象,当该参数为空时抛出异常。
将当前对象与传入的对象进行比较,返回负整数,零,或正整数,分别表示当前对象小于,等于或大于传入对象。
-
Comparator
Comparators能够充当一些排序方法的参数(比如Collections.sort或者Arrays.sort)来控制排序方式,Comparators也能用于对一些数据结构进行排序(比如sorted sets或者sorted maps),或给一些没有自然排序功能(即没有实现Comparable接口的)的Collections对象增加排序功能。
package java.util;
import java.util.Comparators;
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
...
}
注解@FunctionalInterface标明该接口是一个函数式接口,支持lambda表达式或者作为方法引用的目标对象。
该接口主要有两个方法compare和equals,由于java中的类都继承了java.lang.Object,所以equals无需手动实现,因此我们只需要实现compare方法,而接口中剩余方法都提供了默认实现。
Comparator中的compare与Comparable中的compareTo方法类似,不过由于Comparator是一个函数式接口,因此其compare必须包含两个泛型参数,即所要比较的两个对象,compare方法返回正数,零或负数表示第一个对象小于,等于或大于第二个对象。
2. 实际应用
简单说来,任何实现了Comparable接口的类都支持排序,比如Java中的Integer、Character、String类,或实现了该接口的自定义类等,下面通过一个例子来说明。
现在有一个Employee类,已经实现了Comparable接口。
public class Employee implements Comparable<Employee>{
private int id;
private String name;
private int salary;
/**
* 薪资从低到高
* @param o 所要比较的对象
* @return
*/
@Override
public int compareTo(Employee o) {
return salary - o.salary;
}
}
主函数如下所示:
public static void main(String[] args){
Employee employee1 = new Employee(1,"张三", 15000);
Employee employee2 = new Employee(2,"李四", 18500);
Employee employee3 = new Employee(3,"王五", 19000);
Employee employee4 = new Employee(4,"陈六", 20000);
List<Employee> employees = new ArrayList<>();
employees.add(employee4);
employees.add(employee2);
employees.add(employee3);
employees.add(employee1);
// Collections.sort(employees);
for(Employee employee : employees){
System.out.println(employee);
}
}
输出如下所示,此时按照添加元素的顺序输出
取消主函数中第13行代码注释后,此时按照薪资从低到高输出结果:
当一个类没有实现Comparable接口,此时不支持默认排序。在不改动类的定义,并想要给这个类添加排序功能的情况下,Comparator就能派上用场了,仍然通过Employee来举例(类的构造函数以及getter,setter均已省略)。
public class Employee {
private int id;
private String name;
private int salary;
@Override
public String toString() {
return "id: " + id + " 姓名:" + name + " 薪资:" + salary;
}
}
由于没有实现Comparable接口,此时无法直接调用Collections.sort(employees)来实现排序的,我们可以定义一个比较器Comparator,然后作为sort方法的第2个参数。
public static void main(String[] args){
Employee employee1 = new Employee(1,"张三", 15000);
Employee employee2 = new Employee(2,"李四", 18500);
Employee employee3 = new Employee(3,"王五", 19000);
Employee employee4 = new Employee(4,"陈六", 20000);
Employee employee5 = new Employee(5,"钱八", 20000);
Employee[] employees = new Employee[]{employee4,employee2,employee3,employee1,employee5};
Comparator<Employee> comparator = new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return o1.getSalary() - o2.getSalary();
}
};
//传入比较器comparator
Arrays.sort(employees, comparator);
for(Employee employee : employees){
System.out.println(employee);
}
}
效果与类实现Comparable接口是一样的
除了在Collections.sort和Arrays.sort中使用这两个接口,还可以在sorted set和sorted map中发挥作用。
下面给出在sorted map中使用comparator的一个例子,SortedMap是集合框架中的一个接口。此接口扩展了Map 接口并提供了其元素的总排序(元素可以按键的排序顺序遍历)。而TreeMap则实现了SortedMap接口。
public static void main(String[] args) {
Employee employee1 = new Employee(1,"张三", 15000);
Employee employee2 = new Employee(2,"李四", 18500);
Employee employee3 = new Employee(3,"王五", 19000);
Employee employee4 = new Employee(4,"陈六", 20000);
Employee employee5 = new Employee(5,"钱八", 20000);
SortedMap<String, Employee> sortedMap = new TreeMap<>();
sortedMap.put("b", employee3);
sortedMap.put("c", employee2);
sortedMap.put("a", employee5);
sortedMap.put("e", employee1);
sortedMap.put("d", employee4);
// 此时sortedMap默认按照key值从小到大排序
for(Map.Entry<String, Employee> entry: sortedMap.entrySet()){
System.out.println(entry.getKey() + " :{" + entry.getValue() + "}");
}
System.out.println("Comparator:" + sortedMap.comparator());
}
SortedMap默认是按照key进行排序的,我们在自定义comparator的时候也是根据id来的,但如果我们非要让它按照map中的值,比如说我非要根据雇员的id来进行排序(个人不建议这么做,也可能是下面的方法有问题)。
首先是定义一个map,包含了所有数据,然后是将这个map作为构造函数参数,初始化自定义的Comparator,最后将这个comparator传入sorted map中,然而此时的sortedMap仅仅只是初始化了,是没有任何数据的,而且只能添加上面map中已经有的数据
public static void main(String[] args) {
Employee employee1 = new Employee(1,"张三", 15000);
Employee employee2 = new Employee(2,"李四", 18500);
Employee employee3 = new Employee(3,"王五", 19000);
Employee employee4 = new Employee(4,"陈六", 20000);
Employee employee5 = new Employee(5,"钱八", 25000);
Map<String, Employee> map = new HashMap<>();
map.put("b", employee3);
map.put("c", employee2);
map.put("a", employee4);
map.put("d", employee1);
Comparator<String> comparator = new MyComparator<String>(map);
SortedMap<String, Employee> sortedMap = new TreeMap<String, Employee>(comparator);
// 拷贝数据
sortedMap.putAll(map);
for(Map.Entry<String, Employee> entry: sortedMap.entrySet()){
System.out.println(entry.getKey() + " :{" + entry.getValue() + "}");
}
System.out.println("Comparator:" + sortedMap.comparator());
}
自定义的Comparator代码如下,其中的Employee类已经实现了Comparable接口(根据id进行比较):
public class MyComparator<K> implements Comparator<K> {
private Map<K, Employee> map;
public MyComparator(Map<K, Employee> map) {
this.map = map;
}
@Override
public int compare(K k1, K k2) {
return map.get(k1).compareTo(map.get(k2));
}
}
按照map中value的值id进行排序结果如下:
总结:
Comparable是“内部”比较器,而Comparator是“外部”比较器,这里的内部和外部是指是否修改类本身的源码。当一个类内部实现了Comparable接口时,表明这个支持“自然排序”,此外还可以(从类外部)构造Comparator来定制排序的方式