集合架構——HashSet
在介紹HashSet集合之前我們先了解下 equals == 和 toString
1,淺談equals與 == 的差別
Java中測試兩個變量相等有兩種方式 “==” 和 equals方法。
對于基本類型變量 == 比較的是字面值(必須都是數值型變量)
對于引用類型 == 比較的是 引用的值(一個位址)
對于自己建立的類,繼承的是Object 類的equals方法,如果該類不覆寫
equals方法,那麼調用時,它還是會去比較對象引用的值(即指向的位址)
對于jar包中的類,哪些類覆寫了該方法,哪些沒有,到具體的時候可以去檢視
該類的API文檔,我們隻需要知道equasl 與 == 的差別即可。
現在有下列需求:
定義兩個學生對象
1,包含姓名 年齡 學号
2,不指定比較方式,比較兩個學生對象是否是同一對象。
代碼如下:
class Student
{
private String name;
private int age;
private String idStr;
public Student (String name,int age,String idStr){
this.name = name;
this.age = age;
this.idStr = idStr;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
public String getIdStr(){
return this.idStr;
}
}
public class MyEquals
{
public static void main(String args[]){
Student stu1 = new Student("李磊",24,"0912");
Student stu2 = new Student("韓梅梅",22,"0934");
System.out.println(stu1 == stu2);
System.out.println(stu1.equals(stu2));
}
}
2,HashSet集合
HashSet集合底層資料結構是哈希表。
HashSet是如何保證元素的唯一性了?
是同過調用存入集合中元素自身的兩個方法:hashCode 和 equals 方法
在這之前,我們先明确兩個問題:
2.1 hashCode值 和 對象的位址 二者的聯系和差別
對于自定義類
當建立一個自定義類,它會繼承hashCode方法。當我們調用hashCode方法時,會傳回一個int資料?這個資料是怎麼得來的了?它與很多内容
相關,與這個對象的記憶體位址,對象上的成員變量等都有關,這些都參與了哈希值的運算。是以可以明确的是:哈希值不是對象的位址。怎
樣判斷引用是否相同?其實是比較引用指向的位址是否相同。
如果簡單的認為hashCode傳回的值是位址值,那麼覆寫此方法,使其傳回值都相同,那麼所有建立的對象不是都相同嗎?顯然不是。
2.2equals方法到底在比什麼
很多地方都提到了 equals方法與 == 的差別 , == 這個操作符是java固有的操作符,它的比較方式是固定的:基本類型數值比較,
引用類型比較。都有自己的規定和計算方法。 但是對于equals方法,它是一個方法,所有類繼承自Object類。由于重載的原因,使其
成為一個很靈活的比較方法,你可以按自己的想法去指定兩個對象如何相等。
這時需要關心的是,檢視誰的equals方法:誰調用檢視誰,看它指定的比較方式。
看下面一段代碼:
public class MyHashSet
{
public static void main(String args[]){
E element = new E();
E element2 = new E();
String str = new String("這不合邏輯!");
sop(element.hashCode());
sop(element2.hashCode());
sop(str.hashCode());//String 覆寫了hashCode方法,有自己計算哈希值的方法
sop(element == element2);
sop(element.equals(element));
//sop(element == str);這句在編譯時就無法通過。
sop(element.equals(str));
}
public static void sop(Object obj){
System.out.println(obj);
}
}
class E
{
public boolean equals(Object obj){
//你可以很不負責的讓兩個對象“相等”
return true;
}
public int hashCode(){
return 60;
}
}
---------- 運作java程式 ----------
60
60
-342913705
false
true
True
從運作結果來看:== 檢查的是兩個對象的位址值是否相同,對于不同類型在編譯時就會出錯。
而指定的equals方法,理論上可以使任何對象“相等”,不過這種相等并無實際意義。
2.3 HashSet集合的存儲方式
有了以上内容做鋪墊,我們在來看看HashSet是如何保證元素的唯一性,就比較清楚了。
你可以把HashSet集合看成是一個裝東西的容器:玻璃瓶,罐子都行。隻是在裝入東西時,要按這個容器的規則進行。
當我們建立一個對象,以自定義對象為例。因為有hashCode方法,這個對象總會産生一個哈希值,不管你的自定義類覆寫hashCode方法與否。
當我們向HashSet容器放元素(或者對象,其實是對象的引用)時,元素放在哪是一個問題。HashSet這個集合的特點是,每個元素都有哈希值,依據元素的哈希值,決定存放位置。
在進行比較時,先調用的是hashCode方法。
當向HashSet集合中存儲元素時,新添加的元素會與已存入的元素做一一比較:
如果比較的兩個元素通過自身hashCode方法算出的哈希值相同,進一步調用 自身的equals方法,如果equals方法傳回值為真,那麼這兩個元素被判定為 同一進制素,如果equals方法傳回假,兩個元素判定為不同。隻是在集合中存 儲位置上有一個關聯。
如果比較的兩個元素通過自身的hashCode方法算出的哈希值不同,那麼不調 用equals方法,也就判定這兩個元素不同。
是以可以進行實驗:
自定義一個類,不覆寫hashCode方法和equals 方法。那麼它會沿用Object類中計算哈希值的方法和equals方法。由于hashCode方法的實作很複雜(目前仍未看懂),但是其計算方式可以最大程度防止出現相同哈希值相同。
至于equals方法就比較簡單,我們可以看看:
public boolean equals (Object obj){
return this == obj;
}也就是檢查其實否為同一對象。
看代碼如何實作:
import java.util.HashSet;
public class MyHashSet2
{
public static void main(String args[]) throws Exception{
HashSet con = new HashSet();
con.add(new Student(21,"Niki"));
con.add(new Student(24,"Caesar"));
con.add(new Student(22,"HanMeiMei"));
con.add(new Student(24,"Caesar"));
sop(con.size());
}
public static void sop(Object obj){
System.out.println(obj.toString());
}
}
class Student
{
private int age;
private String name;
public Student(int age,String name){
super();
this.age = age;
this.name = name;
}
}
---------- 運作java程式 ----------
4
通過結果可以看出,四個Student對象都存入到集合中了。
現在的需求變了,當學生的姓名和年齡相同時,就判定為相同對象(肯定不能用 == ,它是在比較位址值,當建立對象時,就會為其開辟存儲空間,這種實體空間肯定不相同)。如何改寫hashCode方法了??原則上hashCode的計算公式中要包含作為比較标準的那些參數:此處是年齡和姓名。
看看代碼是如何實作的:
import java.util.HashSet;
public class MyHashSet2
{
public static void main(String args[]) throws Exception{
HashSet con = new HashSet();
con.add(new Student(21,"Niki"));
con.add(new Student(24,"Caesar"));
con.add(new Student(22,"HanMeiMei"));
con.add(new Student(24,"Caesar"));
sop(con.size());
}
public static void sop(Object obj){
System.out.println(obj.toString());
}
}
class Student
{
private int age;
private String name;
public Student(int age,String name){
super();
this.age = age;
this.name = name;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public boolean equals(Object obj){ //指定比較方式
if(!(obj instanceof Student))
return false;
Student st = (Student)obj;
return this.name.equals(st.name) && this.age == st.age;
}
public int hashCode(){ //用年齡和姓名做參數計算哈希值
return (name.hashCode()+age*39);
}
}
---------- 運作java程式 ----------
3
輸出完成 (耗時 0 秒) - 正常終止
看到輸出結果是3,證明按照指定的方式實作了元素的存儲。