天天看點

hashCode()與equals()的關聯

一、Hash表資料結構介紹

請參考 ​​哈希表詳解​​

二、equals的内部實作

equals()的定義位于Object.class中:

public boolean equals(Object obj) {
        return (this      

從這裡可以看出,如果不重寫的話,equals預設就是斷定兩個對象的記憶體位址是否相同。如果記憶體位址相同,必然是同一個對象;如果記憶體位址不相同,必然不是同一個對象。

三、hashCode()介紹

(一)hashCode()内部實作

hashCode()的定義位于Object.class中:

public native int hashCode();      

根據這個方法的聲明可知,該方法傳回一個int類型的數值,并且是本地方法,是以在Object類中并沒有給出具體的實作。

小貼士:

1 什麼是native方法

簡單地講,一個Native Method就是一個java調用非java代碼的接口。一個Native Method是這樣一個java的方法:該方法的實作由非java語言實作,比如C或C++。

這個特征并非java所特有,很多其它的程式設計語言都有這一機制,比如在C++中,你可以用extern “C”告知C++編譯器去調用一個C的函數。

2 為什麼要用native方法

(1)與java環境外互動:

有時java應用需要與java外面的環境互動。這是本地方法存在的主要原因,你可以想想java需要與一些底層系統如作業系統或某些硬體交換資訊時的情況。本地方法正是這樣一種交流機制:它為我們提供了一個非常簡潔的接口,而且我們無需去了解java應用之外的繁瑣的細節。

(2)與作業系統互動:

JVM支援着java語言本身和運作時庫,它是java程式賴以生存的平台,它由一個解釋器(解釋位元組碼)和一些連接配接到本地代碼的庫組成。然而不管怎 樣,它畢竟不是一個完整的系統,它經常依賴于一些底層(underneath在下面的)系統的支援。這些底層系統常常是強大的作業系統。通過使用本地方法,我們得以用java實作了jre的與底層系統的互動,甚至JVM的一些部分就是用C寫的,還有,如果我們要使用一些java語言本身沒有提供封裝的作業系統的特性時,我們也需要使用本地方法。

(二)為何使用hashCode()?

對于包含容器類型的程式設計語言來說,基本上都會涉及到hashCode。在Java中也一樣,hashCode方法的主要作用是為了配合基于散列的集合一起正常運作,這樣的散列集合包括HashSet、HashMap以及HashTable。

  為什麼這麼說呢?考慮一種情況,當向基于散列的集合中插入對象時,如何判别在集合中是否已經存在該對象了?(注意:集合中不允許重複的元素存在)

  也許大多數人都會想到調用equals方法來逐個進行比較,這個方法确實可行。但是如果集合中已經存在一萬條資料或者更多的資料,如果采用equals方法去逐一比較,效率必然是一個問題。此時hashCode方法的作用就展現出來了,當集合要添加新的對象時,先調用這個對象的hashCode方法,得到對應的hashcode值,實際上在HashMap的具體實作中會用一個table儲存已經存進去的對象的hashcode值,如果table中沒有該hashcode值,它就可以直接存進去,不用再進行任何比較了;如果存在該hashcode值, 就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的位址,是以這裡存在一個沖突解決的問題,這樣一來實際調用equals方法的次數就大大降低了,說通俗一點:Java中的hashCode方法就是根據一定的規則将與對象相關的資訊(比如對象的存儲位址,對象的字段等)映射成一個數值,這個數值稱作為散列值。下面這段代碼是java.util.HashMap的中put方法的具體實作:

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
}      

  put方法是用來向HashMap中添加新的元素,從put方法的具體實作可知,會先調用hashCode方法得到該元素的hashCode值,然後檢視table中是否存在該hashCode值,如果存在則調用equals方法重新确定是否存在該元素,如果存在,則更新value值,否則将新的元素添加到HashMap中。從這裡可以看出,hashCode方法的存在是為了減少equals方法的調用次數,進而提高程式效率。

  有些朋友誤以為預設情況下,hashCode傳回的就是對象的存儲位址,事實上這種看法是不全面的,确實有些JVM在實作時是直接傳回對象的存儲位址,但是大多時候并不是這樣,隻能說可能存儲位址有一定關聯。

四、兩者的關系

(一)如果equals方法得到的結果為true,則兩個對象的hashcode是否相等?

例1:重寫equals()但不重寫hashCode()

Student.java

public class Student
    private int age;
    private String name;

    public Student() {
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) 
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}      

Test.java

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        LinkedList<Student> list = new LinkedList<Student>();
        Set<Student> set = new HashSet<Student>();
        Student stu1  = new Student(3,"Li Si");
        Student stu2  = new Student(3,"Li Si");
        System.out.println("stu1 == stu2 : "+(stu1 == stu2));
        System.out.println("hashcode of stu1: " + stu1.hashCode());
        System.out.println("hashcode of stu2: " + stu2.hashCode());
        System.out.print("stu1.hashCode()==stu2.hashCode(): ");
        System.out.println(stu1.hashCode()==stu2.hashCode());

        System.out.println("stu1.equals(stu2) : "+stu1.equals(stu2));
        list.add(stu1);
        list.add(stu2);
        System.out.println("list size:"+ list.size());

        set.add(stu1);
        set.add(stu2);
        System.out.println("set size:"+ set.size());      

運作結果:

stu1 == stu2 : false
hashcode of stu1: 1291472364
hashcode of stu2: 1158801519
stu1.hashCode()==stu2.hashCode(): false
stu1.equals(stu2) : true
list size:2
set size:2      

例2:重寫equals()和hashCode()

Student.java

public class Student
    private int age;
    private String name;

    public Student() {
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    @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 == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}      

Test.java的代碼與例1中的代碼一緻,在此不再羅列。

運作結果:

stu1 == stu2 : false
hashcode of stu1: 73350135
hashcode of stu2: 73350135
stu1.hashCode()==stu2.hashCode(): true
stu1.equals(stu2) : true
list size:2
set size:1      

例3:重寫equals()和hashCode()

Student.java

public class Student
    private int age;
    private String name;
    private static int index = 5;

    public Student() {
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override  
    public int hashCode() {  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + age + index;
        index++;
        result = prime * result + ((name == null) ? 0 : name.hashCode());  
        return result;  
    } 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) 
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}      

Test.java的代碼與例1中的代碼一緻,在此不再羅列。

運作結果:

stu1 == stu2 : false
hashcode of stu1: 73350290
hashcode of stu2: 73350321
stu1.hashCode()==stu2.hashCode(): false
stu1.equals(stu2) : true
list size:2
set size:2      

結論:從上面三個例子可以看出,如果equals方法得到的結果為true,則兩個對象的hashcode可能相等,也可能不相等。

(二)如果equals方法得到的結果為false,則兩個對象的hashcode值是否不相等?

例4:不重寫equals()和hashCode()

Student.java

public class Student {
    private int age;
    private String name;

    public Student() {
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
}      

Test.java

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        LinkedList<Student> list = new LinkedList<Student>();
        Set<Student> set = new HashSet<Student>();
        Student stu1  = new Student(3,"Zhang San");
        Student stu2  = new Student(4,"Li Si");
        System.out.println("stu1 == stu2 : "+(stu1 == stu2));
        System.out.println("hashcode of stu1: " + stu1.hashCode());
        System.out.println("hashcode of stu2: " + stu2.hashCode());
        System.out.print("stu1.hashCode()==stu2.hashCode(): ");
        System.out.println(stu1.hashCode()==stu2.hashCode());

        System.out.println("stu1.equals(stu2) : "+stu1.equals(stu2));
        list.add(stu1);
        list.add(stu2);
        System.out.println("list size:"+ list.size());

        set.add(stu1);
        set.add(stu2);
        System.out.println("set size:"+ set.size());      

運作結果:

stu1 == stu2 : false
hashcode of stu1: 595755354
hashcode of stu2: 1291472364
stu1.hashCode()==stu2.hashCode(): false
stu1.equals(stu2) : false
list size:2
set size:2      

例5:重寫hashCode()

Test.java

public class Student
    private int age;
    private String name;

    public Student() {
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public int hashCode() {
        return 1;
    }
}      

Test.java與例4中的Test.java代碼一緻,在此不再羅列。

運作結果:

stu1 == stu2 : false
hashcode of stu1: 1
hashcode of stu2: 1
stu1.hashCode()==stu2.hashCode(): true
stu1.equals(stu2) : false
list size:2
set size:2      

結論:由例4和例5可以看出,如果equals方法得到的結果為false,則兩個對象的hashcode值可能相等,也可能不相等。

(三)如果兩個對象的hashcode值相等,則equals方法得到的結果未是否為true?

(四)如果兩個對象的hashcode值不相等,則equals方法得到的結果是否為false?