我們知道TreeSet與HashSet都實現了Set<E>,Set的特性就是不允許重複的元素。《thinking in Java》中說set必須定義equals方法確保對象的唯一性,但是經過測試貌似不是這樣,不知道是不是java1.5以後版本改變後造成的。下面就從唯一性展開問題,先看例子:
先來看HashSet:
package testsortset;
import java.util.HashSet;
import java.util.TreeSet;
public class TestHashSet {
public static void main(String[] args) {
HashSet<B> setsB = new HashSet<B>();
setsB.add(new B(1));
setsB.add(new B(2));
setsB.add(new B(3));
setsB.add(new B(3));
B b4 = new B(4);
B b4_ = new B(4);
setsB.add(b4);
setsB.add(b4_);
System.out.println(b4.equals(b4_));
for (B b : setsB) {
System.out.println(b.i);
}
}
}
class B {
public int i;
public B(int n) {
this.i = n;
}
// @Override
// public boolean equals(Object obj) {
// return (obj instanceof B) && (this.i == ((B) obj).i);
// }
//
// @Override
// public int hashCode() {
// return i;
// }
}
輸出:
false
3
4
1
2
3
4
第一個false,很好理解,因爲我們沒有覆蓋equals方法,類B繼承自Object的equals方法,比較的是類內存地址的hashcode。
這裏我估計註釋了equals和hashcode方法,確實,hashset中包含了重複的元素。接下來打開註釋(先只打開equals方法):
package testsortset;
import java.util.HashSet;
import java.util.TreeSet;
public class TestHashSet {
public static void main(String[] args) {
HashSet<B> setsB = new HashSet<B>();
setsB.add(new B(1));
setsB.add(new B(2));
setsB.add(new B(3));
setsB.add(new B(3));
B b4 = new B(4);
B b4_ = new B(4);
setsB.add(b4);
setsB.add(b4_);
System.out.println(b4.equals(b4_));
for (B b : setsB) {
System.out.println(b.i);
}
}
}
class B {
public int i;
public B(int n) {
this.i = n;
}
@Override
public boolean equals(Object obj) {
return (obj instanceof B) && (this.i == ((B) obj).i);
}
// @Override
// public int hashCode() {
// return i;
// }
}
輸出:
true
3
4
1
2
3
4
第一個true很好理解,因爲我們覆蓋了equals方法。但是爲什麼還是含有重複的元素呢?!爲什麼和書上說的不一樣,確定唯一性由equals方法確定呢?先不着急,再打開hashcode方法的註釋看看:
package testsortset;
import java.util.HashSet;
import java.util.TreeSet;
public class TestHashSet {
public static void main(String[] args) {
HashSet<B> setsB = new HashSet<B>();
setsB.add(new B(1));
setsB.add(new B(2));
setsB.add(new B(3));
setsB.add(new B(3));
B b4 = new B(4);
B b4_ = new B(4);
setsB.add(b4);
setsB.add(b4_);
System.out.println(b4.equals(b4_));
for (B b : setsB) {
System.out.println(b.i);
}
}
}
class B {
public int i;
public B(int n) {
this.i = n;
}
@Override
public boolean equals(Object obj) {
return (obj instanceof B) && (this.i == ((B) obj).i);
}
@Override
public int hashCode() {
return i;
}
}
輸出:
true
1
2
3
4
是的,現在你看到了你想要的結果。確定了唯一性。我們可以知道hashset確定唯一性由equals和hashcode兩個方法同時確定。當然,另一種情況註釋equals,留下hashcode的情況可以試一下也是一樣的包含重複的元素,這裏就不貼代碼了。
爲什麼呢?
原因:我們知道hashset底層是由hashmap實現的,也就是說是否包含重複元素由hashmap確定,我們可以看一下hashmap的put方法:(hashmap 1.8的實現是數組+鏈表+紅黑樹)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
從源碼可以看出,當hashmap在put一個元素時,確定該元素是否已經存在hashcode和equals兩個方法確定的。接下來看TreeSet:
package testsortset;
import java.util.TreeSet;
public class TestSortedSet {
public static void main(String[] args) {
TreeSet<A> sets = new TreeSet<A>();
sets.add(new A(1));
sets.add(new A(2));
sets.add(new A(3));
sets.add(new A(3));
A a4 = new A(4);
A a4_ = new A(4);
sets.add(a4);
sets.add(a4_);
System.out.println(a4.equals(a4_));
for (A a : sets) {
System.out.println(a.i);
}
}
}
class A implements Comparable<A> {
public int i;
public A(int n) {
this.i = n;
//System.out.println(i);
}
@Override
public int compareTo(A o) {
// if (o.i > this.i) {
// return -1;
// } else if (o.i < this.i) {
// return 1;
// } else {
// return 0;
// }
return 0;
}
@Override
public boolean equals(Object obj) {
// return (obj instanceof A) && (this.i == ((A) obj).i);
return false;
}
}
輸出:
false
1
第一個false很好理解,equals不管怎樣都返回false。爲什麼只有一個元素呢?不是由equals方法確定嗎?應該每個元素都不相等,應該是多個元素的啊。我們改一改(我們知道sortedSet中的元素要實現comparable接口或者使用comparator):
package testsortset;
import java.util.TreeSet;
public class TestSortedSet {
public static void main(String[] args) {
TreeSet<A> sets = new TreeSet<A>();
sets.add(new A(1));
sets.add(new A(2));
sets.add(new A(3));
sets.add(new A(3));
A a4 = new A(4);
A a4_ = new A(4);
sets.add(a4);
sets.add(a4_);
System.out.println(a4.equals(a4_));
for (A a : sets) {
System.out.println(a.i);
}
}
}
class A implements Comparable<A> {
public int i;
public A(int n) {
this.i = n;
}
@Override
public int compareTo(A o) {
if (o.i > this.i) {
return -1;
} else if (o.i < this.i) {
return 1;
} else {
return 0;
}
}
@Override
public boolean equals(Object obj) {
return (obj instanceof A) && (this.i == ((A) obj).i);
}
}
輸出:
true
1
2
3
4
發現這纔是你預期的效果,是的。猜想,是不是由compareTo方法來確定是否唯一的?我們再做一個測試:
package testsortset;
import java.util.TreeSet;
public class TestSortedSet {
public static void main(String[] args) {
TreeSet<A> sets = new TreeSet<A>();
sets.add(new A(1));
sets.add(new A(2));
sets.add(new A(3));
sets.add(new A(3));
A a4 = new A(4);
A a4_ = new A(4);
sets.add(a4);
sets.add(a4_);
System.out.println(a4.equals(a4_));
for (A a : sets) {
System.out.println(a.i);
}
}
}
class A implements Comparable<A> {
public int i;
public A(int n) {
this.i = n;
}
@Override
public int compareTo(A o) {
if (o.i > this.i) {
return -1;
} else if (o.i < this.i) {
return 1;
} else {
return 0;
}
}
@Override
public boolean equals(Object obj) {
// return (obj instanceof A) && (this.i == ((A) obj).i);
return true;
}
}
輸出:
true
1
2
3
4
第一個true很好理解,後邊的元素不是一個就說明跟equals沒有關係,而是跟compareTo方法有關係,也就是說treeset的唯一性由compareTo方法確定。爲什麼(treeset的底層由treeMap實現)?那麼看看treeMap的put方法:
<span style="font-size:14px;">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) {//是否有comparator
do {
parent = t;
cmp = cpr.compare(key, t.key);//根據comparator比較的結果確定插入樹中的左子樹,右子樹還是設值
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);////根據compareTo比較的結果確定插入樹中的左子樹,右子樹還是設值
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;
}</span>
從源碼中我們可以知道,確定TreeSet唯一性的是Comparable或者Comparator,跟equals方法沒有關係。
總結:set集合具有唯一元素的特性,而不同具體的set的集合有差別。
存入HashSet的元素必須定義hashcode和equals方法,共同確定唯一性。並且它是沒有順序的。
存入TreeSet的元素必須實現接口Comparable或者傳入比較實現Comparator,equals根據具體情況覆蓋,跟唯一性沒有關係。由於TreeSet有紅黑樹實現,所以是有序的。