Java集合架構總結(3)——TreeSet類的排序問題

來源:互聯網
上載者:User

標籤:public   可變對象   void   成功   封裝類   出錯   art   存在   它的   

Java集合架構總結(3)——TreeSet類的排序問題

 

TreeSet支援兩種排序方法:自然排序和定製排序。TreeSet預設採用自然排序。

 

1、自然排序

    TreeSet會調用集合元素的compareTo(Object obj)方法來比較元素之間大小關係,然後將集合元素按升序排列,這種方式就是自然排序。(比較的前提:兩個對象的類型相同)。

 

    java提供了一個Comparable介面,該介面裡定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現該介面的類必須實現該方法,實現了該介面的類的對象就可以比較大小。當一個對象調用該方法與另一個對象進行比較,例如obj1.comparTo(obj2),如果該方法返回0,則表明這兩個對象相等;如果返回一個正整數,則表明obj1大於obj2;如果該方法返回一個負整數,則表明obj1小於obj2.

 

java常用類實現Comparable介面,並提供了比較大小的標準。實現Comparable介面的常用類:

  • BigDecimal、BigIneger以及所有數值型對應封裝類:按它們對應的數值的大小進行比較。
  • Character:按字元的UNICODE值進行比較。
  • Boolean:true對應的封裝類執行個體大於false對應的封裝類執行個體。
  • String:按字串中字元的UNICODE值進行比較。
  • Date、Time:後面的時間、日期比前面的時間、日期大。

如果試圖把一個對象添加進TreeSet時,則該對象的類必須實現Comparable介面。

如下程式則會報錯:

class Err 


public class TestTreeSetError 

    public static void main(String[] args) 
    { 
        TreeSet ts = new TreeSet(); 
        //向TreeSet集合中添加兩個Err對象 
        ts.add(new Err()); 
        ts.add(new Err()); 
    } 
}

 

說明:

    上面程式試圖向TreeSet集合中添加2個Err對象,添加第一個對象時,TreeSet裡沒有任何元素,所以沒有問題;當添加第二個Err對象時,TreeSet就會調用該對象的compareTo(Object obj)方法與集合中其他元素進行比較——如果對應的類沒有實現Comparable介面,則會引發ClassCastException異常。而且當試圖從TreeSet中取出元素第一個元素時,依然會引發ClassCastException異常。

 

    當採用compareTo(Object obj)方法比較對象時,都需要將被比較對象obj強制類型轉換成相同類型,因為只有相同類的兩個執行個體才能比較大小。即向TreeSet中添加的應該是同一個類的對象,否則會引發ClassCastException異常。例如,當向TreeSet中添加一個字串對象,這個操作完全正常。當添加第二個Date對象時,TreeSet就好調用該對象的compareTo(Object obj)方法與集合中其他元素進行比較,則此時程式會引發異常。

    在實際編程中,程式員可以定義自己的類向TreeSet中添加多種類型的對象,前提是使用者自訂類實現了Comparable介面,實現該介面時在實現compareTo(Object obj)方法時沒有進行強制類型轉換。但當操作TreeSet裡的集合資料時,不同類型的元素依然會發生ClassCastExceptio異常。(認真閱讀下就會明白)

 

    當把一個對象加入TreeSet集合中時,TreeSet調用該對象的compareTo(Object obj)方法與容器中的其他對象比較大小,然後根據紅/黑樹狀結構演算法決定它的儲存位置。如果兩個對象通過compareTo(Object obj)比較相等,TreeSet即認為它們儲存同一位置。

    

    對於TreeSet集合而言,它判斷兩個對象不相等的標準是:兩個對象通過equals方法比較返回false,或通過compareTo(Object obj)比較沒有返回0——即使兩個對象時同一個對象,TreeSet也會把它們當成兩個對象進行處理。

如下程式所示:


//Z類,重寫了equals方法,總是返回false, 
//重寫了compareTo(Object obj)方法,總是返回正整數 
class Z implements Comparable 

    int age; 
    public Z(int age) 
    { 
        this.age = age; 
    } 
    public boolean equals(Object obj) 
    { 
        return false; 
    } 
    public int compareTo(Object obj) 
    { 
        return 1; 
    } 

public class TestTreeSet 

    public static void main(String[] args) 
    { 
        TreeSet set = new TreeSet(); 
        Z z1 = new Z(6); 
        set.add(z1); 
        System.out.println(set.add(z1)); 
        //下面輸出set集合,將看到有2個元素 
        System.out.println(set); 
        //修改set集合的第一個元素的age屬性 
        ((Z)(set.first())).age = 9; 
        //輸出set集合的最後一個元素的age屬性,將看到也變成了9 
        System.out.println(((Z)(set.last())).age); 
    } 
}

程式運行結果:

true 
[[email protected], [email protected]] 
9

說明:

    程式中把同一個對象添加了兩次,因為z1對象的equals()方法總是返回false,而且compareTo(Object obj)方法總是返回1。這樣TreeSet會認為z1對象和它自己也不相同,因此TreeSet中添加兩個z1對象。而TreeSet對象儲存的兩個元素實際上是同一個元素。所以當修改TreeSet集合裡第一個元素的age屬性後,該TreeSet集合裡最後一個元素的age屬性也隨之改變了。

 

    總結:當需要把一個對象放入TreeSet中時,重寫該對象對應類的equals()方法時,應保證該方法與compareTo(Object obj)方法有一致結果,其規則是:如果兩個對象通過equals方法比較返回true時,這兩個對象通過compareTo(Object obj)方法比較應返回0.

 

    如果兩個對象通過equals方法比較返回true,但這兩個對象通過compareTo(Object obj)方法比較不返回0時,這將導致TreeSet將會把這兩個對象儲存在不同位置,從而兩個對象都可以添加成功,這與Set集合的規則有點出入。

 

如果兩個對象通過compareTo(Object obj)方法比較返回0時,但它們通過equals方法比較返回false時將更麻煩:因為兩個對象通過compareTo(Object obj)方法比較相等,TreeSet將試圖把它們儲存在同一個位置,但實際上又不行(否則將只剩下一個對象),所以處理起來比較麻煩。

 

    如果向TreeSet中添加一個可變對象後,並且後面程式修改了該可變對象的屬性,導致它與其他對象的大小順序發生改變,但TreeSet不會再次調整它們的順序,甚至可能導致TreeSet中儲存這兩個對象,它們通過equals方法比較返回true,compareTo(Object obj)方法比較返回0.

如下程式所示:

class R 

    int count; 
    public R(int count) 
    { 
        this.count = count; 
    } 
    public String toString() 
    { 
        return "R(count屬性:" + count + ")"; 
    } 
    public boolean equals(Object obj) 
    { 
        if (obj instanceof R) 
        { 
            R r = (R)obj; 
            if (r.count == this.count) 
            { 
                return true; 
            } 
        } 
        return false; 
    } 
    public int hashCode() 
    { 
        return this.count; 
    } 

public class TestHashSet2 

    public static void main(String[] args) 
    { 
        HashSet hs = new HashSet(); 
        hs.add(new R(5)); 
        hs.add(new R(-3)); 
        hs.add(new R(9)); 
        hs.add(new R(-2)); 
        //列印TreeSet集合,集合元素是有序排列的 
        System.out.println(hs); 
        //取出第一個元素 
        Iterator it = hs.iterator(); 
        R first = (R)it.next(); 
        //為第一個元素的count屬性賦值 
        first.count = -3; 
        //再次輸出count將看到TreeSet裡的元素處於無序狀態 
        System.out.println(hs); 
        hs.remove(new R(-3)); 
        System.out.println(hs); 
        //輸出false 
        System.out.println("hs是否包含count為-3的R對象?" + hs.contains(new R(-3))); 
        //輸出false 
        System.out.println("hs是否包含count為5的R對象?" + hs.contains(new R(5)));

    } 
}

 

程式運行結果:

[R(count屬性:-3), R(count屬性:-2), R(count屬性:5), R(count屬性:9)] 
[R(count屬性:20), R(count屬性:-2), R(count屬性:5), R(count屬性:-2)] 
[R(count屬性:20), R(count屬性:-2), R(count屬性:5), R(count屬性:-2)] 
[R(count屬性:20), R(count屬性:-2), R(count屬性:-2)]

 

 

說明:

    上面程式中的R對象是一個正常重寫了equals方法和comparable方法類,這兩個方法都以R對象的count屬性作為判斷的依據。可以看到程式第一次輸出的結果是有序排列的。當改變R對象的count屬性,程式的輸出結果也發生了改變,而且包含了重複元素。一旦改變了TreeSet集合裡可變元素的屬性,當再視圖刪除該對象時,TreeSet也會刪除失敗(甚至集合中原有的、屬性沒被修改,但與修改後元素相等的元素也無法刪除),所以刪除count

為-2的R對象時,沒有任何元素被刪除;程式可以刪除count為5的R對象,這表明TreeSet可以刪除沒有被修改屬性、且不與其他被修改屬性的對象重複的對象。

 

    總結:與HashSet在處理這些對象時將非常複雜,而且容易出錯。為了讓程式更具健壯,推薦HashSet和TreeSet集合中只放入不可變對象。

 

2、定製排序

    TreeSet的自然排序是根據集合元素的大小,TreeSet將他們以升序排列。如果需要實現定製排序,例如降序,則可以使用Comparator介面。該介面裡包含一個int compare(T o1, T o2)方法,該方法用於比較o1和o2的大小。

   如果需要實現定製排序,則需要在建立TreeSet集合對象時,並提供一個Comparator對象與該TreeSet集合關聯,由該Comparator對象負責集合元素的排序邏輯。

如下程式所示:

class M { 
    int age;

    public M(int age) { 
        this.age = age; 
    }

    public String toString() { 
        return "M對象(age:" + age + ")"; 
    } 
}

public class TestTreeSet3 { 
    public static void main(String[] args) { 
        TreeSet ts = new TreeSet(new Comparator() { 
            public int compare(Object o1, Object o2) {

                M m1 = (M) o1; 
                M m2 = (M) o2;

                if (m1.age > m2.age) { 
                    return -1; 
                } else if (m1.age == m2.age) { 
                    return 0; 
                } else { 
                    return 1; 
                } 
            } 
        }); 
        ts.add(new M(5)); 
        ts.add(new M(-3)); 
        ts.add(new M(9)); 
        System.out.println(ts); 
    } 
}

程式運行結果:

[M對象(age:9), M對象(age:5), M對象(age:-3)]

說明:

    上面程式中建立了一個Comparator介面的匿名內部類對象,該對象負責ts集合的排序。所以當我們把M對象添加到ts集合中時,無須M類實現Comparable介面,因為此時TreeSet無須通過M對象來比較大小,而是由與TreeSet關聯的Comparator對象來負責集合元素的排序。使用定製排序時,TreeSet對集合元素排序時不管集合元素本身的大小,而是由Comparator對象負責集合元素的定序。

 

Java集合架構總結(3)——TreeSet類的排序問題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.