TreeSet是實現Set接口的實現類。所以它存儲的值是唯一的,同時也可以對存儲的值進行排序,排序用的是二叉樹原理。所以要理解這個類,必須先簡單理解一下什么是二叉樹。
-
二叉樹原理簡述
假如有這么一個集合TreeSet<Integer>是[5,11,6,5,23,14]
用二叉樹是怎么排序的呢?
二叉樹遍歷方法比較多,有興趣自己百度看下吧。這里只需要知道元素是怎么插入到二叉樹即可。小的存儲在左邊(負數),大的存儲在右邊(正數),相等不存儲。
-
TreeSet的基本使用
public static void main(String[] args) { TreeSet<Integer> ts = new TreeSet<>(); ts.add(2); ts.add(1); ts.add(3); ts.add(2); ts.add(3); ts.add(1); ts.add(2); System.out.println(ts); } // 輸出結果: [1,2,3]
可以知道,TreeSet集合不僅可以保證集合元素的唯一性,還可以排序。
如果TreeSet里面存儲的是對象呢?會出現什么情況呢?
public static void main(String[] args) { TreeSet<Students> ts = new TreeSet<>(); ts.add(new Students("張三",13)); ts.add(new Students("李四",14)); ts.add(new Students("王五",15)); System.out.println(ts); } // 輸出結果: Exception in thread "main" java.lang.ClassCastException: com.lei.Students cannot be cast to java.lang.Comparable
報錯了,因為集合里面的是對象,對象不能轉換為比較可比較對象。
如果想根據年齡排序,打印出各個對象(toString方法),應該怎么做呢?
在API里面搜索一下Comparable,發現是個接口,那么我們就可以讓Students類實現Comparable接口方法,這樣Students對象就成為了可比較對象了。
Students類實現Comparable接口方法:
public class Students implements Comparable<Students> { private String name; private int age; ...... @Override public int compareTo(Students o) { return this.age-o.age; } }
為什么是this.age-o.age?this.age代表調用時的對象的age,返回的如果是正數(比o.age大),就存儲在右邊。返回的是如果是負數(比o.age小),就存儲在左邊。如果等於0,就不存儲。
這就出問題了,如果兩個人不同名字,同樣年齡,this.age - o.age = 0,不就存不進二叉樹了嗎?
驗證一下:
public static void main(String[] args) { TreeSet<Students> ts = new TreeSet<>(); ts.add(new Students("李四",14)); ts.add(new Students("張三",13)); ts.add(new Students("王五",15)); ts.add(new Students("趙六",13)); System.out.println(ts); } // 輸出結果只有張三、李四、王五
所以需要改進一下Students類的compareTo方法,保證同年齡,但是不同名字的學生也能存進二叉樹。
@Override public int compareTo(Students o) { int num = this.age - o.age; // String類里面已經重寫了compareTo方法 // int compareTo(String anotherString) 按字典順序比較兩個字符串 return num == 0 ? this.name.compareTo(o.name) : num;
這樣就可以把四個不同的對象存儲進來,並且先按照年齡排序,年齡相同的再按照字符串排序。
除了這種方式可以實現排序以外,還有一種方式可以實現排序。
TreeSet有這么一個構造方法:
TreeSet(Comparator<? super E> comparator) 構造一個新的,空的樹集,根據指定的比較器進行排序。
Comparator是什么呢?API文檔看一下:
Interface Comparator<T>,是一個接口,里面有一個要實現的接口方法:
int compare(T o1, T o2) 比較其兩個參數的順序。
例如,我們要對字符串的長度進行排序,長度相同的安裝字符串排序:
public class Test5 { public static void main(String[] args) { TreeSet<String> ts = new TreeSet<>(new SortedByLen()); // 父類引用指向子類對象 Comparator c = new SortedByLen(); ts.add("aaaaaaaaa"); ts.add("wc"); ts.add("nba"); ts.add("cba"); ts.add("chichung"); System.out.println(ts); } } class SortedByLen implements Comparator<String> { @Override public int compare(String o1, String o2) { int num = o1.length() - o2.length(); return num == 0 ? o1.compareTo(o2) : num; } } //輸出結果: [wc, cba, nba, chichung, aaaaaaaaa]
需要注意的是重寫compare方法的o1,o2。o1代表調用的對象,o2代表集合中的對象。
兩種實現排序方式視情況而用。
(1)自然順序(Comparable)
- TreeSet類的add()方法中會把存入的對象提升為Comparable類型
- 調用對象的compareTo()方法和集合中的對象比較
- 根據compareTo()方法返回的結果進行存儲
(2)比較器順序(Comparator)
- 創建TreeSet的時候可以指定一個Comparator
- 如果傳入了Comparator的子類對象,那么TreeSet就會按照比較器中的順序排序
- 調用的對象是compare方法的第一個參數,集合中的對象是compare方法的第二個參數
(3)兩種方式的區別
- TreeSet構造函數什么都不傳,默認按照類中Comparable的順序(沒有就報錯ClassCastException)
- TreeSet如果傳入Comparator,就優先按照Comparator
如果不想保證元素的唯一性,改一下compare方法就可以了,永遠不要讓它返回0。