TreeSet支持兩種排序方法:天然排序和定製排序。TreeSet默認採用天然排序。java
TreeSet會調用集合元素的compareTo(Object obj)方法來比較元素之間大小關係,而後將集合元素按升序排列,這種方式就是天然排序。(比較的前提:兩個對象的類型相同)。程序員
java提供了一個Comparable接口,該接口裏定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現該接口的類必須實現該方法,實現了該接口的類的對象就能夠比較大小。當一個對象調用該方法與另外一個對象進行比較,例如obj1.comparTo(obj2),若是該方法返回0,則代表這兩個對象相等;若是返回一個正整數,則代表obj1大於obj2;若是該方法返回一個負整數,則代表obj1小於obj2.算法
java經常使用類實現Comparable接口,並提供了比較大小的標準。實現Comparable接口的經常使用類:編程
若是試圖把一個對象添加進TreeSet時,則該對象的類必須實現Comparable接口。this
以下程序則會報錯:spa
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()); } }
說明:code
上面程序試圖向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
[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.
以下程序所示:
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集合中只放入不可變對象。
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對象負責集合元素的排序規則。