Java集合框架總結(2)——Set接口的使用

一、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集合中,最後只能輸出一個元素。java

        Set接口中的知識,同時也適用於HashSetTreeSetEnumSet三個實現類。算法

二、HashSet類

          HashSet按Hash算法來存儲集合的元素,所以具備很好的存取和查找性能。數據結構

          HashSet的特色:性能

(1)HashSet不是同步的,多個線程訪問是須要經過代碼保證同步 this

(2)集合元素值可使null。spa

       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]

說明:code

(1)Object類提供的toString方法老是返回該對象實現類的類名+@+hashCode(16進制數)值,因此能夠看到上面程序輸出的結果。能夠經過重寫toString方法來輸出本身但願的形式。orm

(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將會按元素的添加順序來訪問集合裏的元素。

 

三、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採用紅黑樹的數據結構對元素進行排序,具體排序內容會在後續文章中說明
相關文章
相關標籤/搜索