背景
最近在看Java 函数式编程的时候又看到了Comparator<T>
这个接口. 于是花了点时间把它的功能, 使用范围以及其”近亲” Comparable<T>
研究了一下, 于是本文将这两个接口的异同以及常见的一些使用场景总结了一下.
Comparable
首先把这个比较简单的接口放在最开始讲, 这个接口只有一个方法:
该方法接收一个同类型对象, 与当前对象进行”比较操作”, 返回一个整数, 当返回值小于0表示当前对象比接收对象小, 等于0表示两者相等, 大于0表示当前对象大于接收对象.这个接口有非常多的实现类, 可以说是非常重要的一个基础接口, 实现这个接口的类具”自身”具有了可进行自然排序的能力, 进行排序的时候就是根据这个函数返回值来决定两个待比较的对象谁大谁小. 可以这样讲, 这个接口赋予了实现类可比较大小的能力, 由于自身具有比较大小的参考, 此类对象的list 或者 array 可以直接使用
Collections.sort
或者
Arrays.sort
进行排序, 而不需要额外指定比较器; 同时此类对象也可以作为
有序集合
中的元素且不需要额外指定比较器, 举个例子:
如下是一个实现了
Comparable
接口的类
public class TestClassOne implements Comparable<TestClassOne> {
private String name;
private int age;
public TestClassOne(String name, int age){
this.name = name;
this.age = age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
@Override
public int compareTo(TestClassOne o) {
// use age to compare
return getAge() - o.getAge();
}
}
主函数里面构建一个list然后进行排序:
public class MainT1 {
public static void main(String[] args) {
TestClassOne t1 = new TestClassOne("t1", 40);
TestClassOne t2 = new TestClassOne("t2", 10);
TestClassOne t3 = new TestClassOne("t3", 20);
TestClassOne t4 = new TestClassOne("t4", 10);
List<TestClassOne> list = Arrays.asList(t1, t2, t3, t4);
System.out.println("list before sort");
for(TestClassOne item : list){
System.out.println(item.getName());
}
Collections.sort(list);
System.out.println("list after sort");
for(TestClassOne item : list){
System.out.println(item.getName());
}
}
}
程序输出结果:
list before sort
t1
t2
t3
t4
list after sort
t2
t4
t3
t1
在API文档中又看到比较该接口下的compareTo
函数和Object
对象中的equals
函数的差别, 于是在这个地方也总结一下:
从功能上讲, equals
方法只是一个”内在”比较两个对象是否相等的方法,方法本身是赋予了实现方法的类可进行比较是否相等的能力. 因为Object
是所有的类的父类, 所以所有类的对象都可进行比较是否相等, 也可以通过扩展类自己定义两个类对象比较是否相等的标准. 单纯从功能上看compareTo
方法的功能是”涵盖”了equals
方法. 而实际调用时, 两者还有一些差别.
equals
1.任意非空对象的equals
方法接收null
参数返回都是false
;
2.使用默认的Object
中的equals
方法(即是自定义类中没有重写这个方法), 那么判断相等的原则则是看两个变量引用的对象是否为同一个对象(即是等价于var1 == var2);
3.当重写这个方法时, 非常有必要重写Object
中的hashCode
方法, 因为两个相等的对象一定是具有相同的hashCode
反之不成立.
compareTo
1.此方法不可以传入一个null
参数, 因为null
不属于任何一个类, 传入null
会抛出NullPointerException
.
2.当调用compareTo
返回值为0(即是比较的两个对象相等的时候) 和调用 equals
返回为true
时区别是什么呢? 这是一个很有意思的问题, 可以这样理解, compareTo
返回0 代表这两个对象从”自然顺”序角度看是相等的, 而equals
返回true
则更加严格的表示这两个对象相等, 是一个东西. 举个例子, 一个班的人按照身高由低到高排序, 小明和小李身高一样, 那么此时小明.compareTo(小李) == 0
而 小明.equals(小李) == false
. 他们只是身高一样, 但是他们是两个人. 从编程的角度讲推荐equals
和 compareTo() == 0
的情况下两者一致.
Comparator
如果说Comparable
赋予了类”内在”的可进行自然排序的能力, 那么Comparator
则是”外在” 的排序手段. Comparator
接口更为复杂, 除了compare()
这个唯一的抽象方法之外, 还有一些静态方法和默认方法, 同时它也是一个标准的函数式编程接口, 在Java函数式编程领域也是非常的常见. 本质上讲它代表一个比较器, 可以用来比较传递给它的两个对象的大小. 这个接口的实现可以用在一些排序工具方法(Collections.sort
或者 Arrays.sort
) 中作为排序的控制器或者在一些具有顺序的数据结构中控制存储数据的顺序. compare
方法接受两个参数, 返回0 表示两者相等, 大于0表示前者大于后者, 小于0 表示前者小于后者.举个例子:
与上例同样的初始list
根据age
进行反向排序
public class MainT1 {
public static void main(String[] args) {
TestClassOne t1 = new TestClassOne("t1", 40);
TestClassOne t2 = new TestClassOne("t2", 10);
TestClassOne t3 = new TestClassOne("t3", 20);
TestClassOne t4 = new TestClassOne("t4", 10);
List<TestClassOne> list = Arrays.asList(t1, t2, t3, t4);
System.out.println("list before sort");
for(TestClassOne item : list){
System.out.println(item.getName());
}
Collections.sort(list, new Comparator<TestClassOne>() {
@Override
public int compare(TestClassOne o1, TestClassOne o2) {
int res = o1.compareTo(o2);
if(res == 0) return 0;
else if (res > 0) return -1;
else return 1;
}
});
/*
此处可以使用lambda表达式替换掉内部匿名类,本身是函数式编程接口
Collections.sort(list, (item1, item2) -> {
int res = item1.compareTo(item2);
if(res == 0) return 0;
else if(res >0) return -1;
else return 1;
});*/
System.out.println("list after sort");
for(TestClassOne item : list){
System.out.println(item.getName());
}
// the different between compareTo and equals
}
}
输出结果:
list before sort
t1
t2
t3
t4
list after sort
t1
t3
t2
t4
对比两个例子, 可以深刻的明白 Comparator
是一个外部的比较器, 他可以决定操作的两个对象的大小关系, 并且它并不要求操作的对象具有内部排序的特性(即是实现了Comparable接口), 举个例子:
我们定义一个final class 并且此类没有实现Comparable
接口
public final class TestClassTwo {
private String name;
private int age;
public TestClassTwo(String name, int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
主类代码:
public class MainT2 {
public static void main(String[] args) {
TestClassTwo t1 = new TestClassTwo("t1", 10);
TestClassTwo t2 = new TestClassTwo("t2", 40);
TestClassTwo t3 = new TestClassTwo("t3", 30);
TestClassTwo t4 = new TestClassTwo("t4", 20);
List<TestClassTwo> list = Arrays.asList(t1, t2, t3, t4);
System.out.println("list before sort");
for(TestClassTwo item : list){
System.out.println(item.getName());
}
Collections.sort(list, (item1, item2) ->{
return item1.getAge() - item2.getAge();
});
System.out.println("list after sort");
for(TestClassTwo item : list){
System.out.println(item.getName());
}
}
}
运行结果:
list before sort
t1
t2
t3
t4
list after sort
t1
t4
t3
t2
小结
Comparable
赋予了实现类内在可比较功能, 实现类的接口可以在需要排序的地方直接使用. 而Comparator
是一个外在的比较器, 他可以作为一个工具更改已经排序的数据结构中的数据顺序, 也可以赋予不具有排序功能的对象可排序的能力.