深入理解Java中HashCode方法
關(guān)于hashCode,維基百科中:
In the Java programming language, every class implicitly or explicitly provides a hashCode() method, which digests the data stored in an instance of the class into a single hash value (a 32-bit signed integer).
hashCode就是根據(jù)存儲(chǔ)在一個(gè)對(duì)象實(shí)例中的所有數(shù)據(jù),提取出一個(gè)32位的整數(shù),該整數(shù)的目的是用來標(biāo)示該實(shí)例的唯一性。有點(diǎn)類似于MD5碼,每個(gè)文件都能通過MD5算法生成一個(gè)唯一的MD5碼。不過,Java中的hashCode并沒有真正的實(shí)現(xiàn)為每個(gè)對(duì)象生成一個(gè)唯一的hashCode,還是會(huì)有一定的重復(fù)幾率。
先來看看Object類,我們知道,Object類是java程序中所有類的直接或間接父類,處于類層次的最高點(diǎn)。在Object類里定義了很多我們常見的方法,包括我們要講的hashCode方法,如下
public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
注意到hashCode方法前面有個(gè)native的修飾符,這表示hashCode方法是由非java語言實(shí)現(xiàn)的,具體的方法實(shí)現(xiàn)在外部,返回內(nèi)存對(duì)象的地址。
在java的很多類中都會(huì)重寫equals和hashCode方法,這是為什么呢?最常見的String類,比如我定義兩個(gè)字符相同的字符串,那么對(duì)它們進(jìn)行比較時(shí),我想要的結(jié)果應(yīng)該是相等的,如果你不重寫equals和hashCode方法,他們肯定是不會(huì)相等的,因?yàn)閮蓚€(gè)對(duì)象的內(nèi)存地址不一樣。
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
其實(shí)這段代碼是這個(gè)數(shù)學(xué)表達(dá)式的實(shí)現(xiàn)
s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
s[i]是string的第i個(gè)字符,n是String的長(zhǎng)度。那為什么這里用31,而不是其它數(shù)呢?《Effective Java》是這樣說的:之所以選擇31,是因?yàn)樗莻€(gè)奇素?cái)?shù),如果乘數(shù)是偶數(shù),并且乘法溢出的話,信息就會(huì)丟失,因?yàn)榕c2相乘等價(jià)于移位運(yùn)算。使用素?cái)?shù)的好處并不是很明顯,但是習(xí)慣上都使用素?cái)?shù)來計(jì)算散列結(jié)果。31有個(gè)很好的特性,就是用移位和減法來代替乘法,可以得到更好的性能:31*i==(i<<5)-i?,F(xiàn)在的VM可以自動(dòng)完成這種優(yōu)化。
可以看到,String類是用它的value值作為參數(shù)來計(jì)算hashCode的,也就是說,相同的value就一定會(huì)有相同的hashCode值。這點(diǎn)也很容易理解,因?yàn)関alue值相同,那么用equals比較也是相等的,equals方法比較相等,則hashCode一定相等。反過來不一定成立。它不保證相同的hashCode一定有相同的對(duì)象。
一個(gè)好的hash函數(shù)應(yīng)該是這樣的:為不相同的對(duì)象產(chǎn)生不相等的hashCode。
在理想情況下,hash函數(shù)應(yīng)該把集合中不相等的實(shí)例均勻分布到所有可能的hashCode上,要想達(dá)到這種理想情形是非常困難的,至少java沒有達(dá)到。因?yàn)槲覀兛梢钥吹?,hashCode是非隨機(jī)生成的,它有一定的規(guī)律,就是上面的數(shù)學(xué)等式,我們可以構(gòu)造一些具有相同hashCode但value值不一樣的,比如說:Aa和BB的hashCode是一樣的。
如下代碼:
public class Main {
public static void main(String[] args) {
Main m = new Main();
System.out.println(m);
System.out.println(Integer.toHexString(m.hashCode()));
String a = "Aa";
String b = "BB";
System.out.println(a.hashCode());
System.out.println(b.hashCode());
}
}
輸出結(jié)果:
Main@2a139a55 2a139a55 2112 2112
一般在重寫equal函數(shù)時(shí),也要重寫hashCode函數(shù),這是為什么呢?
來看看這個(gè)例子,讓我們創(chuàng)建一個(gè)簡(jiǎn)單的類Employee
public class Employee
{
private Integer id;
private String firstname;
private String lastName;
private String department;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
}
上面的Employee類只是有一些非?;A(chǔ)的屬性和getter、setter.現(xiàn)在來考慮一個(gè)你需要比較兩個(gè)employee的情形。
public class EqualsTest {
public static void main(String[] args) {
Employee e1 = new Employee();
Employee e2 = new Employee();
e1.setId(100);
e2.setId(100);
//Prints false in console
System.out.println(e1.equals(e2));
}
}
毫無疑問,上面的程序?qū)⑤敵鰂alse,但是,事實(shí)上上面兩個(gè)對(duì)象代表的是通過一個(gè)employee。真正的商業(yè)邏輯希望我們返回true。
為了達(dá)到這個(gè)目的,我們需要重寫equals方法。
public boolean equals(Object o) {
if(o == null)
{
return false;
}
if (o == this)
{
return true;
}
if (getClass() != o.getClass())
{
return false;
}
Employee e = (Employee) o;
return (this.getId() == e.getId());
}
在上面的類中添加這個(gè)方法,EauqlsTest將會(huì)輸出true。
So are we done?沒有,讓我們換一種測(cè)試方法來看看。
import java.util.HashSet;
import java.util.Set;
public class EqualsTest
{
public static void main(String[] args)
{
Employee e1 = new Employee();
Employee e2 = new Employee();
e1.setId(100);
e2.setId(100);
//Prints 'true'
System.out.println(e1.equals(e2));
Set<Employee> employees = new HashSet<Employee>();
employees.add(e1);
employees.add(e2);
//Prints two objects
System.out.println(employees);
}
上面的程序輸出的結(jié)果是兩個(gè)。如果兩個(gè)employee對(duì)象equals返回true,Set中應(yīng)該只存儲(chǔ)一個(gè)對(duì)象才對(duì),問題在哪里呢?
我們忘掉了第二個(gè)重要的方法hashCode()。就像JDK的Javadoc中所說的一樣,如果重寫equals()方法必須要重寫hashCode()方法。我們加上下面這個(gè)方法,程序?qū)?zhí)行正確。
@Override
public int hashCode()
{
final int PRIME = 31;
int result = 1;
result = PRIME * result + getId();
return result;
}
需要注意記住的事情
盡量保證使用對(duì)象的同一個(gè)屬性來生成hashCode()和equals()兩個(gè)方法。在我們的案例中,我們使用員工id。
eqauls方法必須保證一致(如果對(duì)象沒有被修改,equals應(yīng)該返回相同的值)
任何時(shí)候只要a.equals(b),那么a.hashCode()必須和b.hashCode()相等。
兩者必須同時(shí)重寫。
總結(jié)
以上就是本文關(guān)于深入理解Java中HashCode方法的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!
相關(guān)文章
springboot2.5.6集成RabbitMq實(shí)現(xiàn)Topic主題模式(推薦)
這篇文章主要介紹了springboot2.5.6集成RabbitMq實(shí)現(xiàn)Topic主題模式(推薦),pom.xml引入依賴和常量類創(chuàng)建,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-11-11
Java14發(fā)布了,再也不怕NullPointerException了
這篇文章主要介紹了Java14發(fā)布了,再也不怕NullPointerException了,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2020-03-03
Springboot接收文件與發(fā)送文件實(shí)例教程
最近工作中遇到個(gè)需求,springboot簡(jiǎn)單的上傳文檔或者圖片,并且進(jìn)行操作,操作完后進(jìn)行保存指定路徑,下面這篇文章主要給大家介紹了關(guān)于Springboot接收文件與發(fā)送文件的相關(guān)資料,需要的朋友可以參考下2023-05-05
java實(shí)現(xiàn)的日期時(shí)間轉(zhuǎn)換工具類完整示例
這篇文章主要介紹了java實(shí)現(xiàn)的日期時(shí)間轉(zhuǎn)換工具類,結(jié)合完整實(shí)例形式分析了java針對(duì)日期時(shí)間常見的轉(zhuǎn)換、計(jì)算、格式化等相關(guān)操作與封裝技巧,需要的朋友可以參考下2019-10-10
SpringMVC源碼解讀之HandlerMapping - AbstractUrlHandlerMapping系列re
這篇文章主要介紹了SpringMVC源碼解讀之HandlerMapping - AbstractUrlHandlerMapping系列request分發(fā) 的相關(guān)資料,需要的朋友可以參考下2016-02-02
詳解在java中進(jìn)行日期時(shí)間比較的4種方法
這篇文章主要介紹了詳解在java中進(jìn)行日期時(shí)間比較的4種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Java日志相關(guān)技術(shù)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java日志相關(guān)技術(shù)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理的相關(guān)資料,需要的朋友可以參考下2017-07-07
Java Spring事務(wù)使用及驗(yàn)證過程詳解
這篇文章主要介紹了Java Spring事務(wù)使用及驗(yàn)證過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12

