引言: 最近在處理一個問題,大致是這個樣子,從數據庫里面取出一個集合,取出來的數據放到一個JavaBean里面。結果得到的集合長度為1.
TreeSetSet的一個實現,默認實現排序;故TreeSet的泛型類型必須是Comparable或者Comparator。TreeSet基於TreeMap實現。
實例
public class Person implements Comparable<Person>{
private String name;
private int order;
public Person(String name , int order){
this.name = name;
this.order = order;
}
public static void main(String [] args){
TreeSet<Person> users = new TreeSet<Person>();
users.add(new Person("Jason Kidd",1));
users.add(new Person("Kobe Bryant",1));
users.add(new Person("Jasme Harden",1));
users.add(new Person("Leborn James",1));
System.out.println(" size= "users.size());
}
// getter and setter
}
size= 1
認知被改寫
上面代碼的結果應該是4,因為我們沒有重寫hashCode和equals方法啊,Set的去重邏輯變了嗎?
重寫上面的例子:
public class Person implements Comparable<Person>{
public static void main(String [] args){
TreeSet<Person> users = new TreeSet<Person>();
users.add(new Person("Jason Kidd",1));
users.add(new Person("Kobe Bryant",2));
users.add(new Person("Jasme Harden",1));
users.add(new Person("Leborn James",1));
System.out.println(" size= "users.size());
}
// getter and setter
}
size= 2
表面看好像是compareTo方法覺得了去重;為了證實這個猜測,進入下面的代碼里面去證實下吧。
源碼分析
理論依據
* <p>Note that the ordering maintained by a set (whether or not an explicit
* comparator is provided) must be <i>consistent with equals</i> if it is to
* correctly implement the {@code Set} interface. (See {@code Comparable}
* or {@code Comparator} for a precise definition of <i>consistent with
* equals</i>.) This is so because the {@code Set} interface is defined in
* terms of the {@code equals} operation, but a {@code TreeSet} instance
* performs all element comparisons using its {@code compareTo} (or
* {@code compare}) method, so two elements that are deemed equal by this method
* are, from the standpoint of the set, equal. The behavior of a set
* <i>is</i> well-defined even if its ordering is inconsistent with equals; it
* just fails to obey the general contract of the {@code Set} interface.
//翻譯
注意,如果要正確實現 Set 接口,則 set 維護的順序(無論是否提供了顯式比較器)必須與 equals 一致。
(關於與 equals 一致 的精確定義,請參閱 Comparable 或 Comparator。)這是因為 Set 接口是按照
equals 操作定義的,但 TreeSet 實例使用它的 compareTo(或 compare)方法對所有元素進行比較
,因此從 set 的觀點來看,此方法認為相等的兩個元素就是相等的。即使 set 的順序與 equals 不一致,
其行為也是 定義良好的;它只是違背了 Set 接口的常規協定。
上面提到了TreeSet的equal方法是依據Comparable或者Comparator接口判斷的,和前面測試的結果一致。
源碼解讀:
代碼流轉:
前面提過了,TreeSet是底層是基於TreeMap存儲數據的:
public class TreeSet{
public TreeSet() {
this(new TreeMap<E,Object>());
}
}
構造TreeSet的時候,並沒有檢查參數類型必須是Comparable或者Comparator的實現類。
public class TreeSet{
private static final Object PRESENT = new Object();
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
}
添加元素的操作,實際就是如何給TreeMap增加元素的操作。
// java.util.TreeMap.java
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
上面一段看起來很長,我們重看其中幾句就好了
- TreeMap內置默認的Comparator比較器,一旦設定,后續所有的比較必須依據此比較器進行;發現不符合比較器方式的,拋出異常。
- 核心的兩個do-while循環,依據比較器結果變量__cmp__,判斷是將值插入的覺提位置。一旦__cmp=0__將value放到相同位置。
相同的key會覆蓋,所有前面測試的時候,集合長度不增加,主要因為compareTo方法返回了0.