Java中Set解析

關係

在JAVA集合的這裏會設計到比較多的東西,所以我們先看一下繼承關係,連接一下在Set這裏到底的先後順序是什麼,有一個宏觀的瞭解:

Set:用於存儲無序(存入和取出的順序不一定相同)元素,值不能重複。

不重複性:

Set中判斷是否相等通過兩個方法:通過計算hashCode值和equals方法來比較。

如果想要讓兩個不同的Person對象視爲相等的,就必須覆蓋Object繼下來的hashCode方法和equals方法,因爲Object  hashCode方法返回的是該對象的內存地址,所以必須重寫hashCode方法,才能保證兩個不同的對象具有相同的hashCode,同時也需要兩個不同對象比較equals方法會返回true。

如下圖是Object類源碼中的hashCode()方法,和String繼承後重寫的hashCode()函數,所以說,每一個對象都有一個hashCode值,如果hashCode值不一樣,那麼還要比較equals函數計算是否相同,如果相同就判斷是相同元素,就不加入,如果不同就會加入。

在這裏順便提一句,equals和==的區別。equals方法,用於比較兩個對象是否相同,它其實就是使用兩個對象的內存地址在比較。Object類中的equals方法內部使用的就是==比較運算符。在開發中要比較兩個對象是否相同,經常會根據對象中的屬性值進行比較,也就是在開發經常需要子類重寫equals方法根據對象的屬性值進行比較。

String類重寫的equals()函數,源碼如下:

我們自己也可以重寫equals()方法,如下:

class Person extends Object{
    int age ;
    public boolean equals(Object obj) {
        if(this == obj){
            return true;
        }
        if(!(obj instanceof Person)){
            return false;
        }
        Person p = (Person)obj;
        return this.age == p.age;
    }
}

此時重寫後的equals()方法便和==是有區別的了。

對equals()重新需要注意五點:

  1 、 對任意引用值X,x.equals(x)的返回值一定爲true;
  2  、 對於任何引用值x,y,當且僅當y.equals(x)返回值爲true時,x.equals(y)的返回值一定爲true;
  3  、 如果x.equals(y)=true, y.equals(z)=true,則x.equals(z)=true ;
  4  、如果參與比較的對象沒任何改變,則對象比較的結果也不應該有任何改變;
  5  、任何非空的引用值X,x.equals(null)的返回值一定爲false 。

言歸正傳,繼續看hashCode,Object的hashCode封裝在了本地,String繼承自Object,它就從寫了hashCode函數,可以看到它自己定義了計算方法來計算出不同的hashCode,源碼如下:

Object類:

String類:

hashSet():

HashSet類,也叫哈希表,存放的是哈希值。HashSet存儲元素的順序並不是按照存入時的順序(set的通性),是按照哈希值來存的所以取數據也是按照哈希值取得。該容器中只能存儲不重複的對象。看一下HashSet的繼承關係,因爲它繼承了Set函數,所以它也具有set的集合,對於判斷重複元素,採用的也是hashCodeequals函數。

對於hashCode相同,equals不同是怎麼存儲的呢?這裏就要看下hashSet的哈希桶結構。

圖中的1、2、3、4是hashCode不同,那麼存在了不同的位置,而1、5、6則屬於hashCode相同,equals不同的三個元素,通過像掛燈籠一樣,一個掛一個的存儲起來。

一個小例子如下:

public class Student {
	private String name;
	private int age;
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if(!(obj instanceof Student)){
			System.out.println("類型錯誤");
			return false;
		}
		Student other = (Student) obj;
		return this.age ==  other.age && this.name.equals(other.name);
	}
}

public class HashSetDemo {
	public static void main(String[] args) {
		//創建HashSet對象
		HashSet hs = new HashSet();
		//給集合中添加自定義對象
		hs.add(new Student("zhangsan",21));
		hs.add(new Student("lisi",22));
		hs.add(new Student("wangwu",23));
		hs.add(new Student("zhangsan",21));
		//取出集合中的每個元素
		Iterator it = hs.iterator();
		while(it.hasNext()){
			Student s = (Student)it.next();
			System.out.println(s);
		}
	}
}

TreeSet:

現在有序的容器不能去重和排序,去重的容器不能排序,那麼有沒有可以排序且去重的集合呢?當然有,這就是我們要說的TreeSet。

public class test {
    public static void main(String[] args) {
        HashSet<Integer> hs = new HashSet<>();
        hs.add(14);
        hs.add(14);
        hs.add(12);
        hs.add(11);
        hs.add(15);
        hs.add(13);
        for (Integer h : hs) {
            System.out.println(h);
        }
    }
}

TreeSet既然能有排序的輸出數據,那麼它一定有自己的排序算法。上面的例子傳入的是一個具體的數字,那麼如果我傳入一個Object類型對象呢?答案是它會報java.lang.ClassCastException異常,那麼沒有辦法傳入了嗎?肯定是有的,這時候就需要我們重寫hs裏面的排序函數了。

看例子:

import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<People> ts = new TreeSet<>();
        ts.add(new People("aa",19));
        ts.add(new People("aa",10));
        ts.add(new People("ee",14));
        ts.add(new People("cc",13));
        ts.add(new People("cc",15));
        for (People t : ts) {
            System.out.println(t);
        }
    }
}

class People implements Comparable {
    private String name;
    private int age;
    public People(String name,int age){
        this.name = name;
        this.age = age;
    }
    public String toString(){
        return this.name+"..."+this.age;
    }
    public int compareTo(Object o) {
        People p = (People) o;
        int num = this.name.compareTo(p.name);
        return num == 0 ? this.age - p.age : num;
    }
}

在這裏,我用People類要實現Comparable接口,如果我們要實現接口,首要的就是要重寫接口裏面的方法了。在JAVA的api中是這麼寫Comparable接口的。

意思就是說commpareTo函數的返回值很重要,如果是一個負整數,那麼說明此對象小於指定對象;返回0,說明相等;返回正整數,則說明是大於指定對象的。根據此我們就可以重寫符合我們規則的commpareTo函數了,具體也如上面的例子所示。

LinkedHashSet:

在HashSet下面有一個子類LinkedHashSet,它是哈希表和雙向鏈表組合的一個數據存儲結構,可以保證元素的插入順序,示例如下。

public class LinkedHashSetDemo {
	public static void main(String[] args) {
   		Set<String> set = new LinkedHashSet<String>();
		set.add("bbb");
		set.add("aaa");
		set.add("abc");
		set.add("bbc");
   		Iterator it = set.iterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}
	}
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章