Java中Comparable和Comparator

本文基于JDK1.8源码。

Comparable接口

​ 将Comparable源码中的所有注释去掉后,代码如下:

1
2
3
4
5
6
package java.lang;
import java.util.*;

public interface Comparable<T> {
public int compareTo(T o);
}

从上面源码可以看到

(1)Comparable是一个泛型接口,泛型是T。

(2)接口中只有一个compareTo方法,返回值为int,它用于比较两个对象的“大小”,如果当前对象小于比较的对象o,返回一个负整数;如果等于比较的对象o,返回0,如果大于比较的对象o,返回一个正整数;

(3)如果compareTo方法中的o对象为null,方法会抛出NullPointerException;

关于compareTo方法实现时,需要注意一下几点:

(1)compareTo方法的实现必须保证sgn(x.compareTo(y))=-sgn(y.compareTo(x)),也就是符号相反,比如说x.compareTo(y)返回值为9,sgn(9)=1,那么y.compareTo(x)返回的一定是一个小于0的整数,比如-7,sgn(-7)=-1。如果前者抛出异常,后者也必须抛出异常;

(2)必须保证序的传递性,也就是说如果x.compareTo(y)>0,并且y.compareTo(z)>0,必然会有x.compareTo(z)>0;

(3)如果x.compareTo(y)==0,则sgn(x.compareTo(z)) == sgn(y.compareTo(z))

(4)一般推荐compareTo方法实现时和equals方法联系起来,满足(x.compareTo(y)==0) == (x.equals(y)),也就是说x.compareTo(y)==0和x.equals(y)有相同的boolean值,也就是说两者要么全部为false,要么全部为true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main.compare;

public class Person implements Comparable<Person> {
private String name;
private Integer age;

public Person(String name, Integer age) {
this.name = name;
this.age = age;
}

@Override
public int compareTo(Person person) {

return this.age - person.age;
}

}

代码中Person类实现了Comparable接口,它是Comparable接口的实现类,按照属性age定义了Person类对象之间的一种“序”。但Person类并未对equals方法进行重写,直接继承了父类Object的equals方法。我们来测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main.compare;

import java.util.*;

public class CompareTest {
public static void main(String[] args) {
Person p1 = new Person("zhangsan", 10);
Person p2 = new Person("lisi", 10);
Person p3 = new Person("wangwu", 10);
SortedSet<Person> sortedSet=new TreeSet();
boolean flag1=sortedSet.add(p1);
boolean flag2=sortedSet.add(p2);
boolean flag3=sortedSet.add(p3);
System.out.println("flag1:"+flag1);
System.out.println("flag2:"+flag2);
System.out.println("flag3:"+flag3);
System.out.println(sortedSet);

}
}

程序运行结果如下:

flag1:true
flag2:false
flag3:false
集合大小:1

测试类中定义了三个Person对象p1、p2和p3,根据equals方法,它们是三个不同的对象,但从上面定义的“序”的角度来看,三个对象大小是一样的(age都是10),当我们将他们加入到一个有序集合中,p2和p3会加入失败,这会产生一些意想不到的结果,所以推荐在实现compareTo方法时,应当与equals方法保持一致。

如果将上面的三个Person对象改为:

1
2
3
Person p1 = new Person("zhangsan", 20);
Person p2 = new Person("lisi", 30);
Person p3 = new Person("wangwu", 10);

那么最终的输出结果会是三个排序好的对象。

​ 事实上,在JDK中实现了Comparable的绝大部分Java核心类中的自然顺序都与equals方法是一致的,当然也有特例,比如java.math.BigDecimal。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main.compare;


import java.math.BigDecimal;
import java.util.*;

public class CompareTest {
public static void main(String[] args) {
BigDecimal d1=new BigDecimal("1.0");
BigDecimal d2=new BigDecimal("1.00");
System.out.println("d1的精度:"+d1.scale());
System.out.println("d2的精度:"+d2.scale());
System.out.println("d1.equals(d2):"+d1.equals(d2));
System.out.println("d1.compareTo(d2):"+d1.compareTo(d2));
}
}

程序运行结果:

d1的精度:1
d2的精度:2
d1.equals(d2):false
d1.compareTo(d2):0

Process finished with exit code 0

程序中定义了两个不同精度的BigDecimal对象d1和d2,对于BigDecimal类,equals会比较对象的值和精度,因为两者精度不同,所以d1.equals(d2)返回false,而compareTo仅仅比较值的大小,d1和d2的大小都是0,所以d1.compareTo(d2)返回0。

按照如下compareTo和equals方法实现就比较合理了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

public class Person implements Comparable<Person> {
private String name;
private Integer age;

public Person(String name, Integer age) {
this.name = name;
this.age = age;
}

@Override
public int compareTo(Person person) {
if (this.name.equals(person.name)){
return this.age - person.age;
}else {
return this.name.compareTo(person.name);
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Person person = (Person) o;

if (name != null ? !name.equals(person.name) : person.name != null) return false;
return age != null ? age.equals(person.age) : person.age == null;
}

@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (age != null ? age.hashCode() : 0);
return result;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

测试类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import java.util.SortedSet;



import java.util.TreeSet;







/**



* Created by leboop on 2020/5/23.



*/



public class MainTest {



public static void main(String[] args) {



Person p1=new Person("zs",20);



Person p2=new Person("zs",30);



Person p3=new Person("ls",20);



Person p4=new Person("zs",20);



SortedSet<Person> sortedSet=new TreeSet<>();



sortedSet.add(p1);



sortedSet.add(p2);



sortedSet.add(p3);



sortedSet.add(p4);



for(Person p:sortedSet){



System.out.println(p);



}



}



}

输出结果:

Person{name=’ls’, age=20}
Person{name=’zs’, age=20}
Person{name=’zs’, age=30}

符合预期。

Comparable小结

(1)可以将Comparable接口看成拥有一种可以给实现类某种“序”的神力,谁实现类该接口就具备了一种“序”,而每个实现类可以拥有不同的“序”;

(2)compareTo方法实现时需要与equals方法保持一致,如果不一致必须写明。

Comparator接口

​ Comparator接口的源码去掉注释后,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package java.util;







import java.io.Serializable;



import java.util.function.Function;



import java.util.function.ToIntFunction;



import java.util.function.ToLongFunction;



import java.util.function.ToDoubleFunction;



import java.util.Comparators;







@FunctionalInterface



public interface Comparator<T> {







int compare(T o1, T o2);







boolean equals(Object obj);







default Comparator<T> reversed() {



return Collections.reverseOrder(this);



}







default Comparator<T> thenComparing(Comparator<? super T> other) {



Objects.requireNonNull(other);



return (Comparator<T> & Serializable) (c1, c2) -> {



int res = compare(c1, c2);



return (res != 0) ? res : other.compare(c1, c2);



};



}







default <U> Comparator<T> thenComparing(



Function<? super T, ? extends U> keyExtractor,



Comparator<? super U> keyComparator)



{



return thenComparing(comparing(keyExtractor, keyComparator));



}







default <U extends Comparable<? super U>> Comparator<T> thenComparing(



Function<? super T, ? extends U> keyExtractor)



{



return thenComparing(comparing(keyExtractor));



}







default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {



return thenComparing(comparingInt(keyExtractor));



}







default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {



return thenComparing(comparingLong(keyExtractor));



}

default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
return thenComparing(comparingDouble(keyExtractor));
}

public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}

@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(false, comparator);
}
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2));
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}
public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
}

public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
}
}

从上面源码可以看到

(1)Comparator也是一个泛型接口,T是泛型;

(2)接口内部包含很多方法,我们只关注int compare(T o1, T o2)方法。

(3)compare方法中的两个参数有一个是null值都会抛出空指针异常;

现在如果也想实现Person类可以按照age可以排序,Person类就不需要直接实现Comparator接口了,做法是单独为Person类提供一个比较器,这个比较器需要实现Comparator接口,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main.compare;

public class Person {
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override

public String toString() {

return "Person{" +

"name='" + name + '\'' +

", age=" + age +

'}';
}
}

单独定义一个Person类的降序比较器,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main.compare;

import java.util.Comparator;

public class DesComparator implements Comparator<Person> {



@Override
public int compare(Person o1, Person o2) {
return o2.getAge()-o1.getAge();
}

}

测试类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package main.compare;


import java.math.BigDecimal;



import java.util.*;



public class CompareTest {



public static void main(String[] args) {



Person p1=new Person("zhangsan",10);



Person p2=new Person("lisi",29);



Person p3=new Person("wangwu",25);



Person p4=new Person("mazi",34);



List<Person> personList=new ArrayList<>();



personList.add(p1);



personList.add(p2);



personList.add(p3);



personList.add(p4);



System.out.println("排序前:"+personList);



Collections.sort(personList,new DesComparator());



System.out.println("排序后:"+personList);



}



}

运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
排序前:[Person{name='zhangsan', age=10}, Person{name='lisi', age=29}, Person{name='wangwu', age=25}, Person{name='mazi', age=34}]



排序后:[Person{name='mazi', age=34}, Person{name='lisi', age=29}, Person{name='wangwu', age=25}, Person{name='zhangsan', age=10}]







Process finished with exit code 0

对Comparator的compare方法实现有如下几点要求:

(1)保证sgn(compare(x, y)) 和 -sgn(compare(y, x))相等,compare(x, y)抛出异常当且仅当compare(y, x)也抛出异常;

(2)保证关系传递性,也就是compare(x, y)>0,并且compare(y, z)>0,则compare(x, z)>0;

(3)如果compare(x, y)==0,则sgn(compare(x, z))==sgn(compare(y, z));

(4)一般推荐实现时,和equals方法相关联,保证compare(x, y)==0 和x.equals(y)要么同时返回false,要么同时返回true

Comparator小结

(1)Comparator可以看成一个比较器接口,它不能赋予给像Person这样的类比较能力,只能创建一个比较器类,而Person类可以使用这个比较器;

Comparable和Comparator比较

(1)Comparable接口可以直接赋予实现类一种“序”的能力,而Comparator不能直接赋予这种能力,它通过创建一个“序”给Person用,打个比方,将Comparable和Comparator看成是具备法力的两位神仙,Comparable可以直接给你一种“序”的能力,此时你直接拥有了“序”的能力,而Comparator不能直接给你这种“序”的能力,但是它可以变出一个“序”的法力棒,你去使用法力棒来实现“序”的能力,当法力棒丢失了,你也就没有“序”的能力了;

(2)Comparable的compareTo方法和Comparator的compare方法实现原则基本相同;