Comparator與Comparable用法與區別


一、概述。

  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方法,所以我們可以對實現類進行一些優化:

  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()); } }); 
  1. 借助JDK1.8的lambda表達式,進一步優化為:
Collections.sort(list, (o1, o2) -> o1.getName().compareTo(o2.getName())); 
  1. 借助JDK1.8中Comparator接口中的新的方法comparing,再次優化:
Collections.sort(list, Comparator.comparing(Student::getName)); 

區別

了解了他們的簡單使用之后,我們可以來簡單分析一下他們的區別了。
相同點:

  1. 兩者都是用來用作對象之間的比較,都可以自定義比較規則;
  2. 兩者都是返回一個描述對象之間關系的int;

不同點:

  1. 實現了Comparable的意思是我可以把自己和另一個對象進行比較;而實現了Comparator的意思是我可以比較其他兩個對象;也就是說Comparable是一個可比較的對象可以將自己與另一個對象進行比較;而Comparator是比較兩個不同的對象。
  2. 使用Comparable需要修改原先的實體類,是屬於一種自然排序。而Comparator則不用修改原先類。
  3. 即使修改了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的方法不太多,總結一下就幾種:

  1. comparing獲取比較器,thenComparing多條件比較器;
  2. reverseOrder與naturalOrder用於Comparable向Comparator的轉換;
  3. nullsFirst和nullsLast用於處理排序字段為null的情況;
  4. 剩余的就是原先的compare和equals方法。

另外使用這兩個接口的過程中,需要注意的點就是對null的檢測,處理。

##################################################################################

不同點。

1、自然排序,定制排序。

2、

3、

4、

注意。

1、由於 null 不是一個類,也不是一個對象,因此在重寫 compareTo 方法時應該注意 e.compareTo(null) 的情況,即使 e.equals(null) 返回 false,compareTo 方法也應該主動拋出一個空指針異常 NullPointerException。

2、

鏈接:https://www.jianshu.com/p/50b561044c60


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM