1. 程式人生 > >HashSet中儲存複雜型別物件

HashSet中儲存複雜型別物件



這個話題還是從一個有問題的程式碼中引申出來的,原始碼如下:

import java.util.*;
class TreeSetTest
{
    public static void main(String[] args)
    {
        HashSet hs=new HashSet();
        Student st1=new Student(1,"zhao1");    
        Student st2=new Student(1,"zhao1");    
        hs.add(st1);
        hs.add(st2);
        System.out.println(hs);
    }
}
class Student  
{
    public Student(int num,String name)
    {
        this.num=num;
        this.name=name;
    }
    public int hashCode()
    {
        return new Integer(num).hashCode();
    }
    public boolean equals(Student st)
    {
        if (name==st.name) return true;
        else return false;
    }
    public String toString()
    {
        return "student "+num+" name:"+name;
    }
    int num;
    String name;
}

為什麼st1和st2兩個物件內容完全一樣,卻還能插入到一個set中呢,set不是不能有重複的物件嗎?

這段程式有兩個主要問題,就要先從Java中兩個面向物件的基本含義說起了:

JAVA中的過載overload:
只要是一個類以及其父類裡有的兩個函式有相同的名字但是不同的引數列表(包括引數型別,引數個數,引數順序3項中的一項或多項)。過載可以在單個類或者兩個具有繼承關係的類中出現。是實現類的多型性的一種重要方式。

JAVA中的覆蓋override
覆蓋只會在類繼承的時候才會出現,覆蓋要求兩個函式的名字和引數列表都完全一樣。

在HashSet判斷是不是重複元素時是使用了equals方法,不過請注意自定義的這個類實際繼承了Object類,而Object類中equals方法的定義如下:

 public boolean equals(Object obj) {
        return (this == obj);
    }

這麼說,這段程式中定義的equals方法是對Object中的equals方法的過載,而不是覆蓋,那麼在HashSet判斷重複元素時,實際呼叫的就是Object.equals 方法,自然是false。

所以該程式第一個需要修改的地方就是equals方法:我們要的是覆蓋不是過載,為了防止這樣問題,可以加上annotation讓Eclipse自己去判斷。

public boolean equals(Object st)
    {
        Student tempStudent= (Student) st;
        if (name==tempStudent.name) return true;
        else return false;
    }

另外,該程式段在自定義類的hashCode方法和equals並不一致,前者是用num作為hashCode方法的依據,而後者是用name作為判斷是不是相同的依據。

1)利用HashSet/HashMap/Hashtable類來儲存資料時,都是根據儲存物件的hashcode值來進行判斷是否相同的,在 hashCode中僅在兩個物件有著相同hashCode()的時候才會呼叫equals方法去比較,因為hashset內部採用對某個數字n進行取餘的 方式對雜湊碼進行區域劃分,也就是說即使雜湊碼不同,他們也可能被劃分在同一個區域。在新增資料時,首先計算hashcode(String 物件的雜湊碼根據以下公式計算: s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 注:使用 int 演算法,這裡 s[i] 是字串的第 i 個字元,n 是字串的長度,^ 表示求冪。(空字串的雜湊值為 0)),發現在一個區域的才會使用equals方法去一一比較同一區域的物件是否相同,否則直接插入。

|           |            |

|  區域1  |   區域2  |  ……

|           |            |

這個區域在實現時採用連結串列的方法。

當呼叫了 HashSet 的 add 方法存放物件 obj , HashSet 會首先呼叫 obj 的 hasCode 方法得到該物件的雜湊碼, HashSet 會使用一個演算法把它的雜湊碼轉換成一個數組下標,該下標“標記”了 obj 的位置。如果這個位置上的連結串列中沒有元素,那麼就把 obj 物件新增到連結串列上。如果這個位置上的連結串列中已經有了元素,則遍歷這個連結串列,呼叫 obj 的 equals 方法,判斷 obj 是否和其中的某個元素重複,如果沒有重複的元素,那麼就將 obj 新增到連結串列上;如果有重複的元素,則不會講 obj 物件存入 HashSet 中。

也就是說,根據雜湊表的定義,為了保障相同的物件被放到相同的雜湊區域,則必須滿足條件:有equals() 返回true=> hashCode() 返回true。因為先判斷的是hashCode的值,換句話說,equals的值為true是hashCode值為true 的充分非必要條件。這樣的話,就不會出現兩個實際相同的物件,僅僅因為不在同一個雜湊區域而被錯誤的加入到雜湊集合中的情況發生了。

2)並且由於連結串列的缺點在於查詢速度慢,所以在我們定義自己的hashCode()和equals()時,為了照顧到雜湊表的效能,也要遵循“equals返回false時,hashCode也為false”

綜合上述1)和2)兩點,若hashCode方法和equals不一致則hashCode()和equals()結果沒有任何關係,也就是說 equals返回true時,hashcode()也可能是false的,這個與雜湊表定義中不允許相同的元素的定義不符合,也不符合雜湊表效能優化的需 要。

得出的結論是:建議hashCode和equals方法的判斷依據最好是一個,也就是所謂的兩個方法相容。

注意:當一個物件被儲存進Hashset中以後,就不能修改這個物件中那些參與計算雜湊值的欄位了,否則,物件修改後的雜湊值與最初儲存進 hashset物件的雜湊值就不同了,在這種情況下,即使在contains方法使用該物件的當前引用作為引數去檢索hashset集合,也將返回找不到 物件的結果,這會導致無法從hashset集合中單獨刪除當前物件,從而造成記憶體洩露。

例項程式碼如下:

package testhashcode;
/**
* @author gnuhpc
*         email: [email protected]
*         blog: 
http://blog.csdn.net/gnuhpc
* @date 2010-1-13
*/
import java.util.*;
class TreeSetTest
{
    public static void main(String[] args)
    {
        HashSet<Student> hs=new HashSet<Student>();
        Student st1=new Student(1,"zhao");    
        Student st2=new Student(2,"qian");
        Student st3=new Student(3,"sun");
        hs.add(st1);
        hs.add(st2);
        hs.add(st3);
        System.out.println(hs);
st1.num=4;//可以試著註釋掉這一行看一看結果
        hs.remove(st1);
        System.out.println(hs);
    }
}
class Student  
{
    public Student(int num,String name)
    {
        this.num=num;
        this.name=name;
    }
    public int hashCode()
    {
        return new Integer(num).hashCode();
    }
    @Override
    public boolean equals(Object st)
    {
        Student tempStudent= (Student) st;
        if (num==tempStudent.num) return true;
        else return false;
    }
    public String toString()
    {
        return "student "+num+" name:"+name;
    }
    int num;
    String name;
}



相關推薦

HashSet儲存複雜型別物件

 這個話題還是從一個有問題的程式碼中引申出來的,原始碼如下: import java.util.*; class TreeSetTest {     public static void main(String[] args)     {         HashSet

Java 往TreeSet集合儲存自定義物件學生,按照學生的年齡進行排序。

Set:無序,不可以重複元素。|--HashSet:資料結構是雜湊表。執行緒是非同步的。保證元素唯一性的原理:判斷元素的hashCode值是否相同。如果相同,還會繼續判斷元素的equals方法,是否為true。|--TreeSet:可以對Set集合中的元素進行排序。底層資料

C# LINQ去重複雜型別物件集合

通過使用LINQ方法語法中的Distinct(),可以去重簡單型別集合,如:int、string等。但如果要去重複雜型別集合,那麼 直接呼叫Distinct()方法是不行的。幸運的是,Distinct()方法可以新增自定義比較方式,簡單型別的去重無非就是型別比較,因為型別簡單

有趣的python:關於python的整數型別物件

通過is運算子我們可以比較兩個物件是否同一個a = 2 b = 1 + 1 a is b 返回一個True a = 1000 b = 999 + 1 a is b 返回一個False 兩個相似的行為返回了不同的結果,可以看出來 python在對於整數的快取上將某

List存放不同型別物件之間的轉換

有時候我們會碰到這種問題:兩個List中存放的物件不一樣,但是大部分的屬性相同,想把其中一個List中的物件加上別的屬性之後變成另一個List中的物件,例如: List<NafmiiMemberInfo> list = req.getNafmiiMemberIn

好東西!sqlite3BLOB資料型別儲存物件運用示例

 1:常用介面 個人比較喜歡sqlite, 使用最方便,唯一的準備工作是下載250K的源;而且作者很熱心,有問必答。 以下演示一下使用sqlite的步驟,先建立一個數據庫,然後查詢其中的內容。2個重要結構體和5個主要函式: sqlite3               *

27-集合--Set及其子類(HashSet+LinkedHashSet+TreeSet)+二叉樹+Comparable+Comparator+雜湊表+HashSet儲存自定義物件+判斷元素唯一的方式

一、Set 1、Set:元素不可以重複,是無序的(存入和取出的順序不一致) 2、Set介面中的方法和Collection中的方法一致 3、Set集合的元素取出方式只有一種:迭代器iterator() Set set = new HashSet(); I

【Java】連結串列儲存物件的問題

  在刷《劍指OFFER》的時候,自己犯了一個錯誤,發現:在連結串列中儲存一個物件時,如果該物件是不斷變化的,則應該建立一個新的物件複製該物件的內容(而不是指向同一個物件),將這個新的物件儲存到連結串列中。如果直接儲存該物件的話,連結串列中的物件也會不斷變化。基本資料型別和String則沒有這種問題。 其實

C++不同型別物件的存放位置

C++中不同型別物件的存放位置 1. 儲存區域 2. 不同型別物件的儲存 2.1 全域性物件 2.2 區域性物件 2.3 靜態區域性物件 2.4 動態物件 在C++中,定義的物件被放在不同的區域中,

《Java》完成一個“將使用者通過鍵盤輸入的文字動態加入到Vector類物件,並顯示此Vector類物件儲存的字串”的應用程式

一、任務目標     完成一個java application應用程式,通過接收使用者通過鍵盤輸入的文字,把每次回車輸入的字串動態加入到Vector類物件中,並顯示此Vector類物件中儲存的字串。   二、Vector類     Vector 類可實現自動增長的物件陣列,提供了

題目9  單鏈表儲存M個整數,設計一個時間複雜度儘可能高效的演算法

單鏈表中儲存M個整數,設計一個時間複雜度儘可能高效的演算法,對於連結串列中絕對值相等的元素(|data|<n),只保留第一次出現的節點,刪除其餘的節點。如:15->(-3)->(-15)->3  得:15->(-3) 

springboot多個不同物件的屬性進行比較,將不同的值用使用陣列查詢出,並儲存在意向表

多表維護 @MethodParameter(desc="orgTenantTypeQueryAll",input="user",postType={},postName="",queryString="",httpMethod="get",userParam="user

MyBati__mapper 取值(#{} 或${}) 以及 parameterType為(基本型別複雜型別)

參考資料: MyBatis學習筆記(三)——parameterType為基本型別時的使用方法 MyBatis的傳入引數parameterType型別   1. MyBatis的傳入引數parameterType型別分兩種    1.1  基本資料型別:int,

jvm 物件在記憶體儲存的佈局

jvm  物件在記憶體中儲存的佈局有三部分:物件頭、例項資料、對齊填充。 1、物件頭:執行時資料、型別指標、陣列長度。 (1)執行時資料:hashcode雜湊碼、鎖狀態標誌、執行緒持有的鎖、GC年齡分代等,有些不是固定不變的,在執行時會根據當時的狀態進行修改。 (2)型別指標:

Java關於基本型別物件包裝器==的問題

這也是最近比較火的一道小題目 不加思索的話往往以為都是true。但是事實不是這樣的 ==運算子也可以應用於物件包裝器物件,只不過檢測的物件是否指向同一區域,所以c==d通常不成立, 然而,Java實現卻有可能讓它成立。 如果將經常出現的值包裝到同一物件中,

Js如何判斷一個物件為陣列型別

在說明如何判斷一個物件為陣列型別前,我們先鞏固下js的資料型別,js一共有六大資料型別:number、string、object、Boolean、null、undefined。 string: 由單引號或雙引號來說明,如"string"; number:陣列型別,比如整數、小數等; Boolea

java物件記憶體佈局的基本型別欄位排列順序

java物件記憶體佈局: mark word class物件指標 類欄位 補齊位 如果是陣列物件,2、3之間應該加上  陣列長度 佈局排列表: 32位jdk 普通物件 32位jdk 陣列物件

SpringCloud工作筆記070---SpringCloud使用Redis儲存List型別資料

    JAVA技術交流QQ群:170933152   看看這邊封裝的redis的工具類: src\main\java\cn\gov\majorproj\scadmin\util\CacheUtils.java 注意,就是用

解決vuex儲存複雜引數(如物件陣列等)重新整理資料丟失問題

我需要在搜尋頁拿到結果之後跳轉到搜尋結果頁並攜帶搜尋結果 嘗試過幾種方法之後最終採用vuex+sessionStorage結合的方法在mutations中 setResultValue(state,flag){ sessionStorage.setItem("re

如何向postgreSQL新增bytea型別的大物件資料

用PostgreSQL-Bytea存BlobDAta,如mdb/mp3/jpg/doc等檔案,試了好幾天,是可以存進去,可是轉出來時老是無法使用,經研究發現它的體積會自動長大,且會以3.31的比率增加。這轉出來檔當然是不能用了。而且我用BlobField.BolbSize去看資料庫中的存檔大小就是這個轉出的S