qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          Java集合框架總結:TreeSet類的排序問題

           程序運行結果:

          true 
          [TreeSet.Z@1fb8ee3, TreeSet.Z@1fb8ee3] 
          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.

            如下程序所示:

          1. class R  
          2. {  
          3. int count;  
          4. public R(int count)  
          5. {  
          6. this.count = count;  
          7. }  
          8. public String toString()  
          9. {  
          10. return "R(count屬性:" + count + ")";  
          11. }  
          12. public boolean equals(Object obj)  
          13. {  
          14. if (obj instanceof R)  
          15. {  
          16. R r = (R)obj;  
          17. if (r.count == this.count)  
          18. {  
          19. return true;  
          20. }  
          21. }  
          22. return false;  
          23. }  
          24. public int hashCode()  
          25. {  
          26. return this.count;  
          27. }  
          28. }  
          29. public class TestHashSet2  
          30. {  
          31. public static void main(String[] args)  
          32. {  
          33. HashSet hs = new HashSet();  
          34. hs.add(new R(5));  
          35. hs.add(new R(-3));  
          36. hs.add(new R(9));  
          37. hs.add(new R(-2));  
          38. //打印TreeSet集合,集合元素是有序排列的  
          39. System.out.println(hs);  
          40. //取出第一個元素  
          41. Iterator it = hs.iterator();  
          42. R first = (R)it.next();  
          43. //為第一個元素的count屬性賦值  
          44. first.count = -3;  
          45. //再次輸出count將看到TreeSet里的元素處于無序狀態  
          46. System.out.println(hs);  
          47. hs.remove(new R(-3));  
          48. System.out.println(hs);  
          49. //輸出false  
          50. System.out.println("hs是否包含count為-3的R對象?" + hs.contains(new R(-3)));  
          51. //輸出false  
          52. System.out.println("hs是否包含count為5的R對象?" + hs.contains(new R(5)));  
          53.  
          54. }  
          55. }

          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接口。

            如下程序則會報錯:

          1. class Err  
          2. {  
          3. }  
          4. public class TestTreeSetError  
          5. {  
          6. public static void main(String[] args)  
          7. {  
          8. TreeSet ts = new TreeSet();  
          9. //向TreeSet集合中添加兩個Err對象 
          10. ts.add(new Err());  
          11. ts.add(new Err());  
          12. }  
          13. }

            說明:

            上面程序試圖向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也會把它們當成兩個對象進行處理。

            如下程序所示:

          1. //Z類,重寫了equals方法,總是返回false, 
          2. //重寫了compareTo(Object obj)方法,總是返回正整數 
          3. class Z implements Comparable  
          4. {  
          5. int age;  
          6. public Z(int age)  
          7. {  
          8. this.age = age;  
          9. }  
          10. public boolean equals(Object obj)  
          11. {  
          12. return false;  
          13. }  
          14. public int compareTo(Object obj)  
          15. {  
          16. return 1;  
          17. }  
          18. }  
          19. public class TestTreeSet  
          20. {  
          21. public static void main(String[] args)  
          22. {  
          23. TreeSet set = new TreeSet();  
          24. Z z1 = new Z(6);  
          25. set.add(z1);  
          26. System.out.println(set.add(z1));  
          27. //下面輸出set集合,將看到有2個元素 
          28. System.out.println(set);  
          29. //修改set集合的第一個元素的age屬性 
          30. ((Z)(set.first())).age = 9;  
          31. //輸出set集合的最后一個元素的age屬性,將看到也變成了9 
          32. System.out.println(((Z)(set.last())).age);  
          33. }  
          34. }


           程序運行結果:

          true 
          [TreeSet.Z@1fb8ee3, TreeSet.Z@1fb8ee3] 
          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.

            如下程序所示:

          1. class R  
          2. {  
          3. int count;  
          4. public R(int count)  
          5. {  
          6. this.count = count;  
          7. }  
          8. public String toString()  
          9. {  
          10. return "R(count屬性:" + count + ")";  
          11. }  
          12. public boolean equals(Object obj)  
          13. {  
          14. if (obj instanceof R)  
          15. {  
          16. R r = (R)obj;  
          17. if (r.count == this.count)  
          18. {  
          19. return true;  
          20. }  
          21. }  
          22. return false;  
          23. }  
          24. public int hashCode()  
          25. {  
          26. return this.count;  
          27. }  
          28. }  
          29. public class TestHashSet2  
          30. {  
          31. public static void main(String[] args)  
          32. {  
          33. HashSet hs = new HashSet();  
          34. hs.add(new R(5));  
          35. hs.add(new R(-3));  
          36. hs.add(new R(9));  
          37. hs.add(new R(-2));  
          38. //打印TreeSet集合,集合元素是有序排列的  
          39. System.out.println(hs);  
          40. //取出第一個元素  
          41. Iterator it = hs.iterator();  
          42. R first = (R)it.next();  
          43. //為第一個元素的count屬性賦值  
          44. first.count = -3;  
          45. //再次輸出count將看到TreeSet里的元素處于無序狀態  
          46. System.out.println(hs);  
          47. hs.remove(new R(-3));  
          48. System.out.println(hs);  
          49. //輸出false  
          50. System.out.println("hs是否包含count為-3的R對象?" + hs.contains(new R(-3)));  
          51. //輸出false  
          52. System.out.println("hs是否包含count為5的R對象?" + hs.contains(new R(5)));  
          53.  
          54. }  
          55. }


            程序運行結果:

          [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對象負責集合元素的排序邏輯。

            如下程序所示:

          1. class M {  
          2. int age;  
          3.  
          4. public M(int age) {  
          5. this.age = age;  
          6. }  
          7.  
          8. public String toString() {  
          9. return "M對象(age:" + age + ")";  
          10. }  
          11. }  
          12.  
          13. public class TestTreeSet3 {  
          14. public static void main(String[] args) {  
          15. TreeSet ts = new TreeSet(new Comparator() {  
          16. public int compare(Object o1, Object o2) {  
          17.  
          18. M m1 = (M) o1;  
          19. M m2 = (M) o2;  
          20.  
          21. if (m1.age > m2.age) {  
          22. return -1;  
          23. else if (m1.age == m2.age) {  
          24. return 0;  
          25. else {  
          26. return 1;  
          27. }  
          28. }  
          29. });  
          30. ts.add(new M(5));  
          31. ts.add(new M(-3));  
          32. ts.add(new M(9));  
          33. System.out.println(ts);  
          34. }  
          35. }

            程序運行結果:

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

            說明:

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




          posted on 2012-05-04 11:51 順其自然EVO 閱讀(307) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          <2012年5月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 太湖县| 酒泉市| 元阳县| 高安市| 鞍山市| 临洮县| 北票市| 曲阜市| 临夏市| 冕宁县| 贵阳市| 长春市| 罗田县| 霍邱县| 昔阳县| 泊头市| 呼图壁县| 肇源县| 天长市| 孟州市| 韶山市| 嘉禾县| 达尔| 思南县| 郎溪县| 宁武县| 中江县| 福州市| 什邡市| 深州市| 崇州市| 酒泉市| 宣城市| 广州市| 博湖县| 成安县| 肇源县| 洮南市| 武穴市| 湘阴县| 双峰县|