1. 程式人生 > >Comparable 和 Comparator 介面的比較

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 是一個外在的比較器, 他可以作為一個工具更改已經排序的資料結構中的資料順序, 也可以賦予不具有排序功能的物件可排序的能力.