Java1.8-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}
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));

區別

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

  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));
}

總結

  ComparatorComparable這兩個接口,一般只要我們涉及到集合的排序,都少不了要與這兩個接口打交道,而平時我們使用Comparator很顯然會更多一些,所以本篇文章主要學習了下兩者的使用,區別,並看了看源碼,學習了JDK8之後Comparator增加的一些方法。

其實Comparator的方法不太多,總結一下就幾種:

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

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


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章