1. 程式人生 > >常見的介面與類 -- Comparable

常見的介面與類 -- Comparable

目錄

 

正文

介面Comparable

  我們在字串中見到過CompareTo方法,知道這個方法是用於比較字串順序的,根據字典順序進行排序。Java中很多類也都有CompareTo方法,甚至於排序演算法的底層組成也是依賴於比較的,而這個比較就是依賴於各種資料型別的CompareTo或者Compare方法。Java中所有的compareTo方法都源於一個介面,那就是Comparable。這個介面只有一個方法,那就是CompareTo。所有想要具有比較功能的類,都建議實現這個介面,而非是自己定義這個功能,這是面向物件的概念(將具有相同功能的事物抽象到一個共同的類或介面),並且為了多型也建議通過實現介面來進行向上轉型,通過介面來操作具體實現,這也是面向介面程式設計要求我們做的。下面我們來具體瞭解一下Comparable介面。

回到頂部

1. 介面概述

檢視API我們發現對Comparable定義如下:

public interface Comparable<T>

  此介面強行對實現它的每個類的物件進行整體排序。這種排序被稱為類的自然排序,類的 compareTo 方法被稱為它的自然比較方法。實現此介面的物件列表(和陣列)可以通過 Collections.sort(和 Arrays.sort)進行自動排序。實現此介面的物件可以用作有序對映中的鍵或有序集合中的元素,無需指定比較器。

  對於類 C(Collections) 的每一個 e1 (Element)和 e2 來說,當且僅當 e1.compareTo(e2) == 0 與 e1.equals(e2)具有相同的 boolean 值時,類 C 的自然排序才叫做與 equals 一致。注意,null 不是任何類的例項,即使e.equals(null) 返回 false,e.compareTo(null) 也將丟擲 NullPointerException。【這裡也給了我們一種比較兩個資料內容的方法compareTo,如果返回值為0說明比較的內容相同,在下一節詳細方法介紹中我們再探討!】。建議(雖然不是必需的)最好使自然排序與 equals 一致。這是因為在使用自然排序與 equals 不一致的元素(或鍵)時,沒有顯式比較器的有序集合(和有序對映表)行為表現“怪異”。尤其是,這樣的有序集合(或有序對映表)違背了根據 equals 方法定義的集合(或對映表)的常規協定。 例如,如果將兩個鍵 a 和 b 新增到沒有使用顯式比較器的有序集合中,使 (!a.equals(b) && a.compareTo(b) == 0),那麼第二個 add 操作將返回 false(有序集合的大小沒有增加),因為從有序集合的角度來看,a 和 b 是相等的。

  實際上,所有實現 Comparable 的 Java 核心類都具有與 equals 一致的自然排序。java.math.BigDecimal 是個例外,它的自然排序將值相等但精確度不同的 BigDecimal 物件(比如 4.0 和 4.00)視為相等。 從數學上講,定義給定類 C 上自然排序的關係式 如下:

      {(x, y)|x.compareTo(y) <= 0}。

 整體排序的商是:

      {(x, y)|x.compareTo(y) == 0}。

它直接遵循 compareTo 的協定,商是 C 的等價關係,自然排序是 C 的整體排序。當說到類的自然排序與 equals一致 時,是指自然排序的商是由類的 equals(Object) 方法定義的等價關係。

    {(x, y)|x.equals(y)}。

此介面是 Java Collections Framework 的成員。

回到頂部

2. 介面方法詳讀

  介面和類一樣都是對共有功能的抽象,不同的是類中的功能是具體的實現,而介面的則是對一個功能的抽象。下面我們瞭解一下Comparable介面的抽象方法,該介面只提供了一個方法,介面方法如下:

int

compareTo(T o) 比較此物件與指定物件的順序。O為要比較的物件

如上所訴,該方法用於比較此物件與指定物件的順序。如果該物件小於、等於或大於指定物件,則分別返回負整數、零或正整數。 根據不同類的實現返回不同,大部分返回1,0和-1三個數,如下測試:

@Test

public void test1(){

Integer i1 = 3;

Integer i2 = 7;

System.out.println(i2.compareTo(i1));//1

}

  • 實現類必須確保對於所有的 x 和 y 都存在 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) 的關係。(這意味著如果 y.compareTo(x) 丟擲一個異常,則 x.compareTo(y) 也要丟擲一個異常。)
  • 實現類還必須確保關係是可傳遞的:(x.compareTo(y)>0 && y.compareTo(z)>0) 意味著 x.compareTo(z)>0。
  • 最後,實現者必須確保 x.compareTo(y)==0 意味著對於所有的 z,都存在 sgn(x.compareTo(z)) == sgn(y.compareTo(z))。 強烈推薦 (x.compareTo(y)==0) == (x.equals(y)) 這種做法,但並不是 嚴格要求這樣做。一般來說,任何實現 Comparable 介面和違背此條件的類都應該清楚地指出這一事實。推薦如此闡述:“注意:此類具有與 equals 不一致的自然排序。”

  在前面的描述中,符號 sgn(expression) 指定 signum 數學函式,該函式根據 expression 的值是負數、零還是正數,分別返回 -1、0 或 1 中的一個值。

  注意會丟擲的異常:ClassCastException - 如果指定物件的型別不允許它與此物件進行比較。

回到頂部

3. 介面方法的實踐操作

  程式設計最好的學習莫過於大量的程式碼,通過程式碼發現問題,下面我們就自己來實踐一下Comparable的方法.

3.1  String和Integer對於compareTo()的實現

  String和Integer這兩個類都實現了Comparable介面,都對compareTo方法進行了實現,下面我們通過原始碼來看一下它們各自對於該方法的具體實現:

複製程式碼
private final char value[];//String的底層是字元陣列  a.compareTo(b)
    public int compareTo(String anotherString) {
        int len1 = value.length;//獲取呼叫該方法的字串的長度a
        int len2 = anotherString.value.length;//獲取比較字串的長度b
        int lim = Math.min(len1, len2);//(a <= b) ? a : b;  min底層程式碼  這句程式碼是為了獲取較短的字串的長度
        char v1[] = value;  //建立兩個字元陣列,分別指向這兩個字串的所在
        char v2[] = anotherString.value;
        //迴圈比較,迴圈次數,是較短的字串的長度,如果用較長的字串的長度,那麼會出現nullPointException
        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            //比較相對應索引的元素,如果元素不同則比較返回中間差距的順序,如果相等,那麼就繼續迴圈比較
            if (c1 != c2) {
                return c1 - c2;//字元對應的Unicode碼錶中的數字,這也就是為什麼說String是按照字典書序比較的,如a比b靠前,那麼a對應的數字比b小,相減返回負數,差多少順序,就返回多少
            }
            k++;
        }
        //如果兩個字串的長度不同,其它都相同,那麼返回的就是長度的差距了
        return len1 - len2;
    }
複製程式碼

我們可以看到String對於compareTo的實現就是依據Unicode碼錶中字元對應的數字來判斷的,返回的是字串長度差或者是字元間在碼錶上的差距,依據具體情況,返回也不同.下面我們看看Integer的compareTo();

複製程式碼
//Integer的compareTo方法,底層依據的是compare方法,這個方法是Comparator介面的一個方法
    public int compareTo(Integer anotherInteger) {
        //實際上Integer的比較是通過Integer中包括的整數來比較的
        return compare(this.value, anotherInteger.value);
    }
    public static int compare(int x, int y) {//a.compateTo(b)
        //如果a比b小,那麼返回-1,相等就是0,否則就是1
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
複製程式碼

有了對String和Integer的compareTo的實現的瞭解,下面我們試著自己來實現一個類的compareTo方法;

複製程式碼
package cn.comparable.introduce;
public class ComparableLength implements Comparable<ComparableLength>{
    @Override
    public int compareTo(ComparableLength o) {
        int len1 = Integer.toString(this.hashCode(), 16).length();
        int len2 = Integer.toString(o.hashCode(), 16).length();
        return (len1 - len2 > 0) ? 1 : -1 ;
    }
    public String sort(ComparableLength cl){
        if(this.compareTo(cl) == 1){
            return "該物件比後者hashcode長度長";
        }else {
            return "該物件比後者hashcode長度短,也可能相等";
        }
    }
}
複製程式碼 複製程式碼
@Test
    public void test3(){
        ComparableLength c1 = new ComparableLength();
        ComparableLength c2 = new ComparableLength();
        ComparableLength c3 = new ComparableLength();
        ComparableLength c4 = c1;
        System.out.println(c1.sort(c2) + c1.hashCode());
        System.out.println(c2.sort(c1));
        System.out.println(c2.sort(c3) + c2.hashCode());
        System.out.println(c1.sort(c3) + c3.hashCode());
        System.out.println(c4.sort(c3) + c4.hashCode());
    }
複製程式碼

如上我們自己也能寫一個比較方法了,但是這個方法只簡單比較了一下物件的hashcode的長度,下面我們比較一下hashcode是否相等,和equals()返回同樣結果,這也是建議我們做的;

複製程式碼
package cn.comparable.introduce;
import org.junit.runner.manipulation.Sortable;
public class ComparableLength implements Comparable<ComparableLength>{
    public String sort(ComparableLength c2){
        if(this.compareTo(c2) == 1)
            return "這兩個物件不是同一個物件";
        else 
            return "這兩個物件是同一個物件";
    }
    @Override
    public int compareTo(ComparableLength o) {
        String str1 = Integer.toString(this.hashCode());
        String str2 = Integer.toString(o.hashCode());
        int len1 = str1.length();
        int len2 = str2.length();
        char[] ch1 = str1.toCharArray();
        char[] ch2 = str2.toCharArray();
        int lim = len1 - len2 ;
        if(lim == 0){
            int k = 0;
            while (k < len1) {
                char char1 = ch1[k];
                char char2 = ch2[k];
                if(char1 != char2)
                    return 1;
                k++;
            }
        }else if(lim != 0)
            return 1;
        return 0;
    }
}
複製程式碼 複製程式碼
@Test
    public void test4(){
        ComparableLength c1 = new ComparableLength();
        ComparableLength c2 = new ComparableLength();
        ComparableLength c3 = new ComparableLength();
        ComparableLength c4 = c1;
        System.out.println(c1.sort(c2) + "--"+c1.equals(c2)+"--"+c1.hashCode());
        System.out.println(c2.sort(c1) + "--"+c2.equals(c1));
        System.out.println(c2.sort(c3) + "--"+c2.equals(c3)+"--"+c2.hashCode());
        System.out.println(c1.sort(c3) + "--"+c1.equals(c3)+"--"+ c3.hashCode());
        System.out.println(c4.sort(c1) + "--"+c4.equals(c1)+"--"+ c4.hashCode());
    }
複製程式碼

我們發現可以實現該功能;

Java中除了提供一個比較方法的介面外,還提供了一個比較器的介面Comparator,下節我們來讀一下Comparator