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;
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方法实现原则基本相同;