概述
Comparator和Comparable兩者都屬於集合框架的一部分,都是用來在對象之間進行比較的,但兩者又有些許的不同,我們先通過一個例子來看一下他們的區別,然後再分別學習下它們的源碼。
先來看一下Comparable的例子,定義實體類Student,實現Comparable,重寫compareTo方法:
public class Student implements Comparable<Student> {
private String name;
private Integer age;
private Integer score;
public Student(String name, Integer age, Integer score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public int compareTo(Student o) {
return this.getName().compareTo(o.getName());
}
}
運行測試
public static void main(String[] args) {
Student student1 = new Student("zhangsan", 1, 80);
Student student2 = new Student("lisi", 3, 90);
Student student3 = new Student("wangwu", 2, 100);
List<Student> list = new ArrayList<>();
list.add(student1);
list.add(student2);
list.add(student3);
Collections.sort(list);
list.stream().forEach(n -> System.out.println(n.toString()));
}
output
Student{name='lisi', age=3, score=90}
Student{name='wangwu', age=2, score=100}
Student{name='zhangsan', age=1, score=80}
Student{name='zhangsan', age=1, score=80}
從上面的例子我們大致瞭解了Comparable接口的使用,也就是說同一個類的對象之間如果要進行比較,需要實現Comparable接口,並且實現compareTo方法。這樣比較的時候就會按照這個規則來進行比較。
再來看一下Comparator的例子,定義實體類Student,
public class Student {
private String name;
private Integer age;
private Integer score;
public Student(String name, Integer age, Integer score) {
this.name = name;
this.age = age;
this.score = score;
}
}
自定義比較器:
class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
if (o1.getAge() > o2.getAge()) {
return 1;
} else if (o1.getAge() < o2.getAge()) {
return -1;
} else {
return 0;
}
}
}
class NameComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());
}
}
進行測試:
public static void main(String[] args) {
Student student1 = new Student("zhangsan", 1, 80);
Student student2 = new Student("lisi", 3, 90);
Student student3 = new Student("wangwu", 2, 100);
List<Student> list = new ArrayList<>();
list.add(student1);
list.add(student2);
list.add(student3);
// 這時候如果直接 Collections.sort(list) 會由於Student沒有默認的自然排序,編譯不過。
Collections.sort(list, new AgeComparator());
list.stream().forEach(n -> System.out.println(n.toString()));
System.out.println("\n-------------------");
Collections.sort(list, new NameComparator());
list.stream().forEach(n -> System.out.println(n.toString()));
}
先按照AgeComparator比較規則進行比較,再按照NameComparator比較器進行比較,output:
Student{name='zhangsan', age=1, score=80}
Student{name='wangwu', age=2, score=100}
Student{name='lisi', age=3, score=90}
-------------------
Student{name='lisi', age=3, score=90}
Student{name='wangwu', age=2, score=100}
Student{name='zhangsan', age=1, score=80}
可以看到,我們如果要對實體類的對象進行比較,在不修改原實體類的情況下,可以通過實現多個Comparator來實現多個比較規則。通過Comparator,我們可以自定義比較規則,針對對象的屬性或者字段等來進行比較,而Comparable就實現不了,因爲它的compareTo方法只能有一種比較規則。
實現Comparator,同樣也要實現它的一個方法compare。由於一般情況下我們實現的Comparator只有一個compare方法,所以我們可以對實現類進行一些優化:
1、使用匿名類來代替單獨的實現類。比如我們可以將
Collections.sort(list, new NameComparator());
替換爲:Collections.sort(list, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getName().compareTo(o2.getName()); } });
2、藉助JDK1.8的lambda表達式,進一步優化爲:
Collections.sort(list, (o1, o2) -> o1.getName().compareTo(o2.getName()));
3、藉助JDK1.8中Comparator接口中的新的方法comparing,再次優化:
Collections.sort(list, Comparator.comparing(Student::getName));
區別
瞭解了他們的簡單使用之後,我們可以來簡單分析一下他們的區別了。
相同點:
- 兩者都是用來用作對象之間的比較,都可以自定義比較規則;
- 兩者都是返回一個描述對象之間關係的int;
不同點:
- 實現了Comparable的意思是
我可以把自己和另一個對象進行比較
;而實現了Comparator的意思是我可以比較其他兩個對象
;也就是說Comparable是一個可比較的對象可以將自己與另一個對象進行比較;而Comparator是比較兩個不同的對象。- 使用Comparable需要修改原先的實體類,是屬於一種自然排序。而Comparator則不用修改原先類。
- 即使修改了Comparable實體類,Comparable也僅有一種比較規則。而Comparator可以實現多個,來提供多個比較規則。
下面來看一下各自的源碼,由於都是接口,我們主要看下JDK1.8之後的默認實現方法。
Comparable
Comparable就比較簡單了,只有一個compareTo方法。我們實現該方法的時候注意一下對象的NPE(NullPointerException)問題就可以了。
Comparator
Comparator除了默認的compare和equals接口之外,其他的基本都是默認實現方法。我們來看一下這些方法的實現。
reversed方法
返回逆序比較的比較器,這個就很簡單,底層直接使用Collections的reverseOrder來實現。
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
thenComparing
這個方法是多條件排序的方法,當我們排序的條件不止一個的時候可以使用該方法。比如說我們對Student先按照age字段排序,再按照score排序,就可以使用thenComparing方法:
Student student1 = new Student("zhangsan", 1, 80);
Student student2 = new Student("lisi", 3, 90);
Student student3 = new Student("wangwu", 2, 100);
Student student4 = new Student("tom", 3, 75);
List<Student> list = new ArrayList<>();
list.add(student1);
list.add(student2);
list.add(student3);
list.add(student4);
Collections.sort(list, Comparator.comparing(Student::getAge).thenComparing(Student::getScore));
list.stream().forEach(n -> System.out.println(n.toString()));
output :
Student{name='zhangsan', age=1, score=80}
Student{name='wangwu', age=2, score=100}
Student{name='tom', age=3, score=75}
Student{name='lisi', age=3, score=90}
如果有需要,我們可以藉助這個方法構造更復雜的排序方式。該方法有多個重載的方法,並且有幾個支持各種類型的方法如:
default <U> Comparator<T> thenComparing( Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
不過,底層調用的全是同樣的方法:
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);
};
}
reverseOrder和naturalOrder
naturalOrder是返回自然排序的比較器,reverseOrder恰好和naturalOrder相反,兩者都是用於返回實現了Comparable接口的對象的比較器。我們藉助剛纔Comparator和Comparable兩者進行比較時的Comparable的代碼,來測試一下,先看一下自然順序,結果和原來一樣:
Collections.sort(list, Comparator.naturalOrder());
output:
Student{name='lisi', age=3, score=90}
Student{name='wangwu', age=2, score=100}
Student{name='zhangsan', age=1, score=80}
再看一下逆序:
Collections.sort(list, Comparator.reverseOrder());
output:
Student{name='zhangsan', age=1, score=80}
Student{name='wangwu', age=2, score=100}
Student{name='lisi', age=3, score=90}
這兩個方法說白了就是將Comparable的方式轉換爲Comparator,因爲Comparable的功能有限,不方便我們基於Comparable進行擴展。底層實現分別藉助於工具類Collections及Comparators來實現。Comparators是專門用於支持Comparator的內部類。
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
nullsFirst和nullsLast方法
這兩個方法有點意思,是說如果排序的字段爲null的情況下這條記錄怎麼排序。nullsFirst是說將這條記錄排在最前面,而nullsLast是說將這條記錄排序在最後面。舉個例子就可以了:
public static void main(String[] args) {
Student student1 = new Student("zhangsan", 1, 80);
Student student2 = new Student("lisi", null, 90);
Student student3 = new Student("wangwu", 2, 100);
List<Student> list = new ArrayList<>();
list.add(student1);
list.add(student2);
list.add(student3);
Comparator<Student> comparator = Comparator.comparing(Student::getAge,
Comparator.nullsLast(Comparator.reverseOrder()));
Collections.sort(list, comparator);
list.stream().forEach(n -> System.out.println(n.toString()));
}
按照age進行逆序排列,將key爲null的排到最後面,output:
Student{name='wangwu', age=2, score=100}
Student{name='zhangsan', age=1, score=80}
Student{name='lisi', age=null, score=90}
按照age進行自然順序排列,將key爲null的排再最前面:
Comparator<Student> comparator = Comparator.comparing(Student::getAge,
Comparator.nullsFirst(Comparator.naturalOrder()));
Collections.sort(list, comparator);
output:
Student{name='lisi', age=null, score=90}
Student{name='zhangsan', age=1, score=80}
Student{name='wangwu', age=2, score=100}
如果多個key都爲null的話,那將無法保證這幾個對象的排序。源碼很簡單,直接通過Comparators內部類實現的。
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);
}
comparing方法
comparing方法我們已經用過,就是獲取對象的比較器也就是比較規則,有幾個重載方法及對應類型的方法,第一個參數接受的是函數式表達式。我們使用例子來看下:
因爲前些時候用到了布爾類型的排序,所以我們這次就拿Boolean類型的排序來進行測試。修改原先Student類,添加一個布爾類型字段likeGame,然後測試下:
public static void main(String[] args) {
List<Student> list = Arrays.asList(
new Student("zhangsan", 1, 80, true),
new Student("wangwu", 3, 100, false),
new Student("zisi", 4, 110, true),
new Student("zisi", 2, 110, true)
);
Collections.sort(list, Comparator.comparing(Student::getLikeGame).thenComparing(Student::getAge));
list.stream().forEach(n -> System.out.println(n.toString()));
}
output:
Student{name='wangwu', age=3, score=100, likeGame=false}
Student{name='zhangsan', age=1, score=80, likeGame=true}
Student{name='zisi', age=2, score=110, likeGame=true}
Student{name='zisi', age=4, score=110, likeGame=true}
這就實現了按照Student對象的likeGame進行自然排序,同樣,兩個參數的接口,第二個參數可以指定具體的比較規則:
Collections.sort(list, Comparator.comparing(Student::getLikeGame, Comparator.reverseOrder())
.thenComparing(Student::getAge));
list.stream().forEach(n -> System.out.println(n.toString()));
這就實現了按照逆序對likeGame進行排序,output:
Student{name='zhangsan', age=1, score=80, likeGame=true}
Student{name='zisi', age=2, score=110, likeGame=true}
Student{name='zisi', age=4, score=110, likeGame=true}
Student{name='wangwu', age=3, score=100, likeGame=false}
簡單說下,Boolean類型的排序默認規則是false排在前面,而true排在後面,原因我們可以看下Boolean的compare方法。
public int compareTo(Boolean b) {
return compare(this.value, b.value);
}
public static int compare(boolean x, boolean y) {
return (x == y) ? 0 : (x ? 1 : -1);
}
comparing接口的源碼可以簡單看下,其中一個源碼如下:
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));
}
總結
Comparator和Comparable這兩個接口,一般只要我們涉及到集合的排序,都少不了要與這兩個接口打交道,而平時我們使用Comparator很顯然會更多一些,所以本篇文章主要學習了下兩者的使用,區別,並看了看源碼,學習了JDK8之後Comparator增加的一些方法。
其實Comparator的方法不太多,總結一下就幾種:
- comparing獲取比較器,thenComparing多條件比較器;
- reverseOrder與naturalOrder用於Comparable向Comparator的轉換;
- nullsFirst和nullsLast用於處理排序字段爲null的情況;
- 剩餘的就是原先的compare和equals方法。
另外使用這兩個接口的過程中,需要注意的點就是對null的檢測,處理。