1、Set接口的使用
Set集合里多個對象之間沒有明顯的順序。具體詳細方法請參考API文檔(可見身邊隨時帶上API文檔有多重要),基本與Collection方法相同。只是行為不同(Set不允許包含重復元素)。
Set集合不允許重復元素,是因為Set判斷兩個對象相同不是使用==運算符,而是根據equals方法。即兩個對象用equals方法比較返回true,Set就不能接受兩個對象。
public class TestSet { public static void main(String[] args) { Set<String> books = new HashSet<String>(); //添加一個字符串對象 books.add(new String("Struts2權威指南")); //再次添加一個字符串對象, //因為兩個字符串對象通過equals方法比較相等,所以添加失敗,返回false boolean result = books.add(new String("Struts2權威指南")); System.out.println(result); //下面輸出看到集合只有一個元素 System.out.println(books); } }
程序運行結果:
false
[Struts2權威指南]
說明:程序中,book集合兩次添加的字符串對象明顯不是一個對象(程序通過new關鍵字來創建字符串對象),當使用==運算符判斷返回false,使用equals方法比較返回true,所以不能添加到Set集合中,最后只能輸出一個元素。
Set接口中的知識,同時也適用於HashSet、TreeSet和EnumSet三個實現類。
2、HashSet類
HashSet按Hash算法來存儲集合的元素,因此具有很好的存取和查找性能。
HashSet的特點:
(1)HashSet不是同步的,多個線程訪問是需要通過代碼保證同步
(2)集合元素值可以使null。
HashSet集合判斷兩個元素相等的標准是兩個對象通過equals方法比較相等,並且兩個對象的hashCode()方法返回值也相等。
//類A的equals方法總是返回true,但沒有重寫其hashCode()方法
class A
{
public boolean equals(Object obj)
{
return true;
}
}
//類B的hashCode()方法總是返回1,但沒有重寫其equals()方法
class B
{
public int hashCode()
{
return 1;
}
}
//類C的hashCode()方法總是返回2,並重寫其equals()方法
class C
{
public int hashCode()
{
return 2;
}
public boolean equals(Object obj)
{
return true;
}
}
public class TestHashSet
{
public static void main(String[] args)
{
HashSet<Object> books = new HashSet<Object>();
//分別向books集合中添加2個A對象,2個B對象,2個C對象
books.add(new A());
books.add(new A());
books.add(new B());
books.add(new B());
books.add(new C());
books.add(new C());
System.out.println(books);
}
}
[B@1, B@1 , C@2 , A@b5dac4 , A@9945ce]
程序運行結果:
說明:
(1)Object類提供的toString方法總是返回該對象實現類的類名+@+hashCode(16進制數)值,所以可以看到上面程序輸出的結果。可以通過重寫toString方法來輸出自己希望的形式。
(2)即使2個A對象通過equals比較返回true,但HashSet依然把它們當成2個對象;即使2個B對象的hashCode()返回相同值,但HashSet依然把它們當成2個對象。即如果把一個對象放入HashSet中時,如果重寫該對象equals()方法,也應該重寫其hashCode()方法。其規則是:如果2個對象通過equals方法比較返回true時,這兩個對象的hashCode也應該相同。
hash算法的功能:
它能保證通過一個對象快速查找到另一個對象。hash算法的價值在於速度,它可以保證查詢得到快速執行。
當需要查詢集合中某個元素時,hash算法可以直接根據該元素的值得到該元素保存位置,從而可以讓程序快速找到該元素。
當從HashSet中訪問元素時,HashSet先計算該元素的hashCode值(也就是調用該對象的hashCode())方法的返回值),然后直接到該hashCode對應的位置去取出該元素。
即也是快速的原因。HashSet中每個能存儲元素的“曹位(slot)”通常稱為“桶(bucket)”,如果多個元素的hashCode相同,但它們通過equals()方法比較返回false,就需要一個“桶”里放多個元素,從而導致性能下降。
繼續深入研究HashSet:
當向HashSet中添加一個可變對象后,並且后面程序修改了該可變對象的屬性,可能導致它與集合中其他元素相同,這就可能導致HashSet中包含兩個相同的對象。
看下面程序:
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<R> hs = new HashSet<R>(); hs.add(new R(5)); hs.add(new R(-3)); hs.add(new R(9)); hs.add(new R(-2)); //打印HashSet集合,集合元素是有序排列的 System.out.println(hs); //取出第一個元素 Iterator<R> it = hs.iterator(); R first = (R)it.next(); //first指向集合的第一個元素 //為第一個元素的count屬性賦值 first.count = -3; //first指向的元素值發生改變,地址並沒有改變,大家可以試着用Java內存分配機制(棧和堆)思考下。 //再次輸出count將看到HashSet里的元素處於無序狀態 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屬性:5), R(count屬性:9), R(count屬性:-3), R(count屬性:-2)]
[R(count屬性:-3), R(count屬性:9), R(count屬性:-3), R(count屬性:-2)]
[R(count屬性:-3), R(count屬性:9), R(count屬性:-2)]
hs是否包含count為-3的R對象?false
hs是否包含count為5的R對象?false
說明:程序重寫了R類的equals()和hashCode()方法,這兩個方法都是根據R對象的count屬性來判斷。從運行結果可以看出,HashSet集合中有完全相同元素,這表明兩個元素已經重復,但因為HashSet在添加它們時已經把它們添加到了不同地方,所以HashSet完全可以容納兩個相同元素。至於第一個count為-3的R對象,它保存在count為5的R對象對應的位置(地址)。當向HashSet中添加可變對象時,必須十分小心。如果修改HashSet集合中的對象,有可能導致該對象與集合中其他對象相等,從而導致HashSet無法准確訪問該對象。
HashSet還有一個子類LinkedHashSet,LinkedHashSet集合也根據元素hashCode值來決定元素存儲位置,但它同時使用鏈表維護元素的次序,即當遍歷LinkedHashSet集合元素時,HashSet將會按元素的添加順序來訪問集合里的元素。
3、TreeSet類
TreeSet是SortedSet接口的唯一實現,TreeSet可以確保集合元素處於排序狀態(元素是有序的)。
TreeSet提供的幾個額外方法:
Comparator comparator(): 返回當前Set使用的Comparator,或者返回null,表示以自然方式排序。
Object first():返回集合中的第一個元素。
Object last():返回集合中的最后一個元素。
Objiect lower(Object e):返回集合中位於指定元素之前的元素(即小於指定元素的最大元素,參考元素可以不是TreeSet的元素)。
Object higher(Object e):返回集合中位於指定元素之后的元素(即大於指定元素的最小元素,參考元素可以不需要TreeSet的元素)。
SortedSet subSet(fromElement, toElement):返回此Set的子集,范圍從fromElement(包含大於等於)到toElement(不包含小於)。
SortedSet headSet(toElement):返回此Set的子集,由小於toElement的元素組成。
SortedSet tailSet(fromElement):返回此Set的子集,由大於或等於fromElement的元素組成。
public class TestTreeSetCommon { public static void main(String[] args) { TreeSet<Integer> nums = new TreeSet<Integer>(); //向TreeSet中添加四個Integer對象 nums.add(5); nums.add(2); nums.add(10); nums.add(-9); //輸出集合元素,看到集合元素已經處於排序狀態 System.out.println(nums); //輸出集合里的第一個元素 System.out.println(nums.first()); //輸出集合里的最后一個元素 System.out.println(nums.last()); //返回小於4的子集,不包含4 System.out.println(nums.headSet(4)); //返回大於5的子集,如果Set中包含5,子集中還包含5 System.out.println(nums.tailSet(5)); //返回大於等於-3,小於4的子集。 System.out.println(nums.subSet(-3 , 4)); } }
程序運行結果:[-9, 2, 5, 10]
-9
10
[-9, 2]
[5, 10]
[2]
說明:由運行結果可以看出,TreeSet並不是根據元素的插入順序進行排序,而是根據元素實際值來進行排序。TreeSet采用紅黑樹的數據結構對元素進行排序,具體排序內容會在后續文章中說明。
文章中代碼和部分內容來源於《瘋狂Java講義》,如需轉載,請注明出處。謝謝。