关于 hashcode 和 equals
首先需要明白 hashCode() 和equals是Object類中已經(jīng)被定義好的,所以在java中定義的任何類都有這兩個(gè)方法。其中原始的equals()方法是用來(lái)比較兩個(gè)對(duì)象的地址值,而原始的hashCode()方法返回其對(duì)象所在的物理地址。看下面一個(gè)例子:
public static void main(String[] args) {Person person1 = new Person(10,"zhangsan");Person person2 = new Person(10,"zhangsan");System.out.println("pserson1 hasCode:" + person1.hashCode());System.out.println("pserson2 hasCode:" + person2.hashCode());System.out.println(person1.equals(person2));} } class Person{int age;String name;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Person(int age, String name) {this.age = age;this.name = name;}
運(yùn)行結(jié)果如下:
?需要注意的一點(diǎn)是,equals()相等的兩個(gè)對(duì)象,hashCode()一定相等,hashCode()相等的話,并一定是相等的兩個(gè)對(duì)象,即equals并一定相等(我的理解是equals比較的是兩個(gè)對(duì)象,而hasCode()是對(duì)象的屬性,對(duì)象相等那么其屬性一定相等,相反其屬性相等但是并不一定是同一個(gè)對(duì)象)。這就要求我們?cè)谥貙懽远x類的時(shí)候如果重寫了equals()方法的話,一定也要重寫hascode()方法。
同時(shí)我們知道Set集合是不允許有重復(fù)的內(nèi)容的。具體判斷set集合中是否有已經(jīng)有該對(duì)象的步驟如下:
1)、判斷兩個(gè)對(duì)象的hashCode是否相等 。
????? 如果不相等,認(rèn)為兩個(gè)對(duì)象也不相等,完畢?
????? 如果相等,轉(zhuǎn)入2)
(這一點(diǎn)只是為了提高存儲(chǔ)效率而要求的,其實(shí)理論上沒(méi)有也可以,但如果沒(méi)有,實(shí)際使用時(shí)效率會(huì)大大降低,所以我們這里將其做為必需的。)?
2)、判斷兩個(gè)對(duì)象用equals運(yùn)算是否相等 。
????? 如果不相等,認(rèn)為兩個(gè)對(duì)象也不相等?
????? 如果相等,認(rèn)為兩個(gè)對(duì)象相等(equals()是判斷兩個(gè)對(duì)象是否相等的關(guān)鍵)
如果想查找一個(gè)集合中是否包含有某個(gè)對(duì)象,大概的程序代碼怎樣寫呢?
你通常是逐一取出每個(gè)元素與要查找的對(duì)象進(jìn)行比較,當(dāng)發(fā)現(xiàn)某個(gè)元素與要查找的對(duì)象進(jìn)行equals方法比較的結(jié)果相等時(shí),則停止繼續(xù)查找并返回肯定的信息,否則,返回否定的信息,如果一個(gè)集合中有很多個(gè)元素,比如有一萬(wàn)個(gè)元素,并且沒(méi)有包含要查找的對(duì)象時(shí),則意味著你的程序需要從集合中取出一萬(wàn)個(gè)元素進(jìn)行逐一比較才能得到結(jié)論。
有人發(fā)明了一種哈希算法來(lái)提高從集合中查找元素的效率,這種方式將集合分成若干個(gè)存儲(chǔ)區(qū)域,每個(gè)對(duì)象可以計(jì)算出一個(gè)哈希碼,可以將哈希碼分組(使用不同的hash函數(shù)來(lái)計(jì)算的),每組分別對(duì)應(yīng)某個(gè)存儲(chǔ)區(qū)域,根據(jù)一個(gè)對(duì)象的哈希嗎就可以確定該對(duì)象應(yīng)該存儲(chǔ)在哪個(gè)區(qū)域HashSet就是采用哈希算法存取對(duì)象的集合,它內(nèi)部采用對(duì)某個(gè)數(shù)字n進(jìn)行取余(這種的hash函數(shù)是最簡(jiǎn)單的)的方式對(duì)哈希碼進(jìn)行分組和劃分對(duì)象的存儲(chǔ)區(qū)域;Object類中定義了一個(gè)hashCode()方法來(lái)返回每個(gè)Java對(duì)象的哈希碼,當(dāng)從HashSet集合中查找某個(gè)對(duì)象時(shí),Java系統(tǒng)首先調(diào)用對(duì)象的hashCode()方法獲得該對(duì)象的哈希碼表,然后根據(jù)哈希嗎找到相應(yīng)的存儲(chǔ)區(qū)域,最后取得該存儲(chǔ)區(qū)域內(nèi)的每個(gè)元素與該對(duì)象進(jìn)行equals方法比較;這樣就不用遍歷集合中的所有元素就可以得到結(jié)論,可見(jiàn),HashSet集合具有很好的對(duì)象檢索性能,但是,HashSet集合存儲(chǔ)對(duì)象的效率相對(duì)要低些,因?yàn)橄騂ashSet集合中添加一個(gè)對(duì)象時(shí),要先計(jì)算出對(duì)象的哈希碼和根據(jù)這個(gè)哈希碼確定對(duì)象在集合中的存放位置為了保證一個(gè)類的實(shí)例對(duì)象能在HashSet正常存儲(chǔ),要求這個(gè)類的兩個(gè)實(shí)例對(duì)象用equals()方法比較的結(jié)果相等時(shí),他們的哈希碼也必須相等;也就是說(shuō),如果obj1.equals(obj2)的結(jié)果為true,那么以下表達(dá)式的結(jié)果也要為true:
obj1.hashCode() == obj2.hashCode()
換句話說(shuō):當(dāng)我們重寫一個(gè)對(duì)象的equals方法,就必須重寫他的hashCode方法,不過(guò)不重寫他的hashCode方法的話,Object對(duì)象中的hashCode方法始終返回的是一個(gè)對(duì)象的hash地址,而這個(gè)地址是永遠(yuǎn)不相等的。所以這時(shí)候即使是重寫了equals方法,也不會(huì)有特定的效果的,因?yàn)閔ashCode方法如果都不想等的話,就不會(huì)調(diào)用equals方法進(jìn)行比較了,所以沒(méi)有意義了。
?
如果一個(gè)類的hashCode()方法沒(méi)有遵循上述要求,那么,當(dāng)這個(gè)類的兩個(gè)實(shí)例對(duì)象用equals()方法比較的結(jié)果相等時(shí),他們本來(lái)應(yīng)該無(wú)法被同時(shí)存儲(chǔ)進(jìn)set集合中,但是,如果將他們存儲(chǔ)進(jìn)HashSet集合中時(shí),由于他們的hashCode()方法的返回值不同(Object中的hashCode方法返回值是永遠(yuǎn)不同的),就是說(shuō)可能會(huì)被set保存兩次的。
看下面這個(gè)例子:
public static void main(String[] args) {Person person1 = new Person(10,"zhangsan");Person person2 = new Person(10,"zhangsan");System.out.println("pserson1 hasCode:" + person1.hashCode());System.out.println("pserson2 hasCode:" + person2.hashCode());System.out.println(person1.equals(person2));Set<Person> set = new HashSet<Person>();set.add(person1);set.add(person2);System.out.println(set.size());} } class Person{int age;String name;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Person(int age, String name) {this.age = age;this.name = name;}@Overridepublic boolean equals(Object o){Person a = (Person) o;return a.name.equals(name) ;}//@Override//public int hashCode(){// return 1;//}
?
這里面我們只是重寫了equals方法,調(diào)用?person1.equals(person2) 也是返回true的,但是最終set.size()是2。這是運(yùn)行結(jié)果
我們?cè)傩薷囊幌吕?#xff0c;重寫一下 hascode方法
@Overridepublic int hashCode(){return age + name.hashCode();}
?
再看一下結(jié)果
我們?cè)賮?lái)看一個(gè)有意思的事情,如果我們把equals直接返回false,那么再調(diào)用set.add(person1),那么按照我們以上的分析,先檢查hashCode() 是否相等,再調(diào)用equals()方法,那么這時(shí)候應(yīng)該返回 set.size() 為3,然而事情的真相真如我們分析的那樣么,先看運(yùn)行結(jié)果;
public static void main(String[] args) {Person person1 = new Person(10,"zhangsan");Person person2 = new Person(10,"zhangsan");System.out.println("pserson1 hasCode:" + person1.hashCode());System.out.println("pserson2 hasCode:" + person2.hashCode());System.out.println(person1.equals(person2));Set<Person> set = new HashSet<Person>();set.add(person1);set.add(person2);set.add(person1);// System.out.println(person1 == person1); System.out.println(set.size());} } class Person{int age;String name;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Person(int age, String name) {this.age = age;this.name = name;}@Overridepublic boolean equals(Object o){//Person a = (Person) o;//return a.name.equals(name) ;return false;}@Overridepublic int hashCode(){return age + name.hashCode();}
?
?
hashCode是基于hashMap實(shí)現(xiàn)的,我們查看一下hashMap的源碼查找一下原因
/*** Associates the specified value with the specified key in this map.* If the map previously contained a mapping for the key, the old* value is replaced.** @param key key with which the specified value is to be associated* @param value value to be associated with the specified key* @return the previous value associated with <tt>key</tt>, or* <tt>null</tt> if there was no mapping for <tt>key</tt>.* (A <tt>null</tt> return can also indicate that the map* previously associated <tt>null</tt> with <tt>key</tt>.)*/public V put(K key, V value) {if (key == null)return putForNullKey(value);int hash = hash(key);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;}
?分析一下?if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 如果 hash一樣的話,會(huì)去判斷 key == key 和 key.equals(k),只要兩個(gè)有一個(gè)符合要求就插入不成功,而對(duì)于同一個(gè)對(duì)象來(lái)說(shuō) ?person1 == person1 永遠(yuǎn)是true,所以set里面還是2,當(dāng)然這種情況很少會(huì)遇到,一般重寫 equals()方法的時(shí)候不會(huì)直接返回 false~。如果非要讓set變成3個(gè)話也行,重寫一下hashCode就可以了,比如我們可以產(chǎn)生一個(gè)隨機(jī)數(shù)?
@Overridepublic int hashCode(){return (int)(Math.random()*100);}
?最后在看一個(gè)刪除時(shí)候應(yīng)該注意的地方:
public static void main(String[] args) {Person person1 = new Person(10, "zhangsan");Person person2 = new Person(10, "zhangsan");//System.out.println("pserson1 hasCode:" + person1.hashCode());//System.out.println("pserson2 hasCode:" + person2.hashCode());//System.out.println(person1.equals(person2));Set<Person> set = new HashSet<Person>();set.add(person1);set.add(person2);set.add(person1);System.out.println("刪除前set.size():" + set.size());System.out.println("更改屬性前person1.hashCode():" + person1.hashCode());person1.setAge(6);System.out.println("更改屬性后person1.hashCode():" + person1.hashCode());set.remove(person1);// System.out.println(person1 == person1);System.out.println("刪除后set.size():" + set.size());}
?運(yùn)行結(jié)果:
?事情又出乎我們的意料之外,我們明明已經(jīng)刪除了person1,但是事實(shí)上并沒(méi)有刪除成功,我在程序中已經(jīng)把原因打印出出來(lái)了,更改了屬性以后,因?yàn)槲覀兊膆ashCode()是根據(jù)屬性值來(lái)生成的,因此屬性更改以后hashCode()也被修改了但是他的存儲(chǔ)位置不會(huì)有變化,當(dāng)刪除的時(shí)候會(huì)按照新的hashCode()去找person1,肯定找不到,所以并沒(méi)有刪除成功,會(huì)導(dǎo)致失敗。
這告訴我們一個(gè)事情:如果對(duì)象的屬性值參與了hashCode的運(yùn)算中,在進(jìn)行刪除的時(shí)候,就不能對(duì)其屬性值進(jìn)行修改,否則會(huì)出現(xiàn)嚴(yán)重的問(wèn)題。
轉(zhuǎn)載于:https://www.cnblogs.com/l919310075/p/7376229.html
總結(jié)
以上是生活随笔為你收集整理的关于 hashcode 和 equals的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 今生相伴下一句是什么呢?
- 下一篇: qian开头的成语有哪些?