Comparable 和 Comparator 接口的比較

背景

最近在看Java 函數式編程的時候又看到了Comparator<T> 這個接口. 於是花了點時間把它的功能, 使用範圍以及其”近親” Comparable<T> 研究了一下, 於是本文將這兩個接口的異同以及常見的一些使用場景總結了一下.

Comparable

首先把這個比較簡單的接口放在最開始講, 這個接口只有一個方法:

Comparable

該方法接收一個同類型對象, 與當前對象進行”比較操作”, 返回一個整數, 當返回值小於0表示當前對象比接收對象小, 等於0表示兩者相等, 大於0表示當前對象大於接收對象.這個接口有非常多的實現類, 可以說是非常重要的一個基礎接口, 實現這個接口的類具”自身”具有了可進行自然排序的能力, 進行排序的時候就是根據這個函數返回值來決定兩個待比較的對象誰大誰小. 可以這樣講, 這個接口賦予了實現類可比較大小的能力, 由於自身具有比較大小的參考, 此類對象的list 或者 array 可以直接使用Collections.sort 或者 Arrays.sort 進行排序, 而不需要額外指定比較器; 同時此類對象也可以作爲有序集合 中的元素且不需要額外指定比較器, 舉個例子:
如下是一個實現了Comparable 接口的類

public class TestClassOne implements Comparable<TestClassOne> {
    private String name;
    private int age;
    public TestClassOne(String name, int age){
        this.name = name;
        this.age = age;
    }
    public String getName(){
        return this.name;
    }
    public int getAge(){
        return this.age;
    }
    @Override
    public int compareTo(TestClassOne o) {
        // use age to compare
        return getAge() - o.getAge();
    }
}

主函數裏面構建一個list然後進行排序:

public class MainT1 {
    public static void main(String[] args) {
        TestClassOne t1 = new TestClassOne("t1", 40);
        TestClassOne t2 = new TestClassOne("t2", 10);
        TestClassOne t3 = new TestClassOne("t3", 20);
        TestClassOne t4 = new TestClassOne("t4", 10);
        List<TestClassOne> list = Arrays.asList(t1, t2, t3, t4);
        System.out.println("list before sort");
        for(TestClassOne item : list){
            System.out.println(item.getName());
        }
        Collections.sort(list);
        System.out.println("list after sort");
        for(TestClassOne item : list){
            System.out.println(item.getName());
        }

    }
}

程序輸出結果:

list before sort
t1
t2
t3
t4
list after sort
t2
t4
t3
t1

在API文檔中又看到比較該接口下的compareTo 函數和Object 對象中的equals 函數的差別, 於是在這個地方也總結一下:
從功能上講, equals 方法只是一個”內在”比較兩個對象是否相等的方法,方法本身是賦予了實現方法的類可進行比較是否相等的能力. 因爲Object 是所有的類的父類, 所以所有類的對象都可進行比較是否相等, 也可以通過擴展類自己定義兩個類對象比較是否相等的標準. 單純從功能上看compareTo 方法的功能是”涵蓋”了equals 方法. 而實際調用時, 兩者還有一些差別.

equals

1.任意非空對象的equals 方法接收null 參數返回都是false;
2.使用默認的Object 中的equals 方法(即是自定義類中沒有重寫這個方法), 那麼判斷相等的原則則是看兩個變量引用的對象是否爲同一個對象(即是等價於var1 == var2);
3.當重寫這個方法時, 非常有必要重寫Object 中的hashCode 方法, 因爲兩個相等的對象一定是具有相同的hashCode 反之不成立.

compareTo

1.此方法不可以傳入一個null 參數, 因爲null 不屬於任何一個類, 傳入null 會拋出NullPointerException.
2.當調用compareTo 返回值爲0(即是比較的兩個對象相等的時候) 和調用 equals 返回爲true 時區別是什麼呢? 這是一個很有意思的問題, 可以這樣理解, compareTo 返回0 代表這兩個對象從”自然順”序角度看是相等的, 而equals 返回true 則更加嚴格的表示這兩個對象相等, 是一個東西. 舉個例子, 一個班的人按照身高由低到高排序, 小明和小李身高一樣, 那麼此時小明.compareTo(小李) == 0小明.equals(小李) == false. 他們只是身高一樣, 但是他們是兩個人. 從編程的角度講推薦equalscompareTo() == 0 的情況下兩者一致.

Comparator

如果說Comparable 賦予了類”內在”的可進行自然排序的能力, 那麼Comparator 則是”外在” 的排序手段. Comparator 接口更爲複雜, 除了compare() 這個唯一的抽象方法之外, 還有一些靜態方法和默認方法, 同時它也是一個標準的函數式編程接口, 在Java函數式編程領域也是非常的常見. 本質上講它代表一個比較器, 可以用來比較傳遞給它的兩個對象的大小. 這個接口的實現可以用在一些排序工具方法(Collections.sort 或者 Arrays.sort) 中作爲排序的控制器或者在一些具有順序的數據結構中控制存儲數據的順序. compare 方法接受兩個參數, 返回0 表示兩者相等, 大於0表示前者大於後者, 小於0 表示前者小於後者.舉個例子:
與上例同樣的初始list 根據age 進行反向排序

public class MainT1 {
    public static void main(String[] args) {
        TestClassOne t1 = new TestClassOne("t1", 40);
        TestClassOne t2 = new TestClassOne("t2", 10);
        TestClassOne t3 = new TestClassOne("t3", 20);
        TestClassOne t4 = new TestClassOne("t4", 10);
        List<TestClassOne> list = Arrays.asList(t1, t2, t3, t4);
        System.out.println("list before sort");
        for(TestClassOne item : list){
            System.out.println(item.getName());
        }
        Collections.sort(list, new Comparator<TestClassOne>() {
            @Override
            public int compare(TestClassOne o1, TestClassOne o2) {
                int res = o1.compareTo(o2);
                if(res == 0) return 0;
                else if (res > 0) return -1;
                else return 1;
            }
        });

        /*
        此處可以使用lambda表達式替換掉內部匿名類,本身是函數式編程接口
        Collections.sort(list, (item1, item2) -> {
            int res = item1.compareTo(item2);
            if(res == 0) return 0;
            else if(res >0) return -1;
            else return 1;
        });*/

        System.out.println("list after sort");
        for(TestClassOne item : list){
            System.out.println(item.getName());
        }

        // the different between compareTo and equals

    }
}

輸出結果:

list before sort
t1
t2
t3
t4
list after sort
t1
t3
t2
t4

對比兩個例子, 可以深刻的明白 Comparator 是一個外部的比較器, 他可以決定操作的兩個對象的大小關係, 並且它並不要求操作的對象具有內部排序的特性(即是實現了Comparable接口), 舉個例子:
我們定義一個final class 並且此類沒有實現Comparable 接口

public final class TestClassTwo {
    private String name;
    private int age;
    public TestClassTwo(String name, int age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

主類代碼:

public class MainT2 {
    public static void main(String[] args) {
        TestClassTwo t1 = new TestClassTwo("t1", 10);
        TestClassTwo t2 = new TestClassTwo("t2", 40);
        TestClassTwo t3 = new TestClassTwo("t3", 30);
        TestClassTwo t4 = new TestClassTwo("t4", 20);
        List<TestClassTwo> list = Arrays.asList(t1, t2, t3, t4);
        System.out.println("list before sort");
        for(TestClassTwo item : list){
            System.out.println(item.getName());
        }
        Collections.sort(list, (item1, item2) ->{
            return item1.getAge() - item2.getAge();
        });
        System.out.println("list after sort");
        for(TestClassTwo item : list){
            System.out.println(item.getName());
        }
    }
}

運行結果:

list before sort
t1
t2
t3
t4
list after sort
t1
t4
t3
t2

小結

Comparable 賦予了實現類內在可比較功能, 實現類的接口可以在需要排序的地方直接使用. 而Comparator 是一個外在的比較器, 他可以作爲一個工具更改已經排序的數據結構中的數據順序, 也可以賦予不具有排序功能的對象可排序的能力.

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