一、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值可能相等,也可能不相等。