一、概述。
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}
從上面的例子我們大致了解了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方法,所以我們可以對實現類進行一些優化:
- 使用匿名類來代替單獨的實現類。比如我們可以將
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()); } });
- 借助JDK1.8的lambda表達式,進一步優化為:
Collections.sort(list, (o1, o2) -> o1.getName().compareTo(o2.getName()));
- 借助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的檢測,處理。
##################################################################################
不同點。
1、自然排序,定制排序。
2、
3、
4、
注意。
1、由於 null 不是一個類,也不是一個對象,因此在重寫 compareTo 方法時應該注意 e.compareTo(null) 的情況,即使 e.equals(null) 返回 false,compareTo 方法也應該主動拋出一個空指針異常 NullPointerException。
2、