关于 hashcode 和 equals
首先需要明白 hashCode() 和equals是Object類中已經被定義好的,所以在java中定義的任何類都有這兩個方法。其中原始的equals()方法是用來比較兩個對象的地址值,而原始的hashCode()方法返回其對象所在的物理地址。看下面一個例子:
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;}
運行結果如下:
?需要注意的一點是,equals()相等的兩個對象,hashCode()一定相等,hashCode()相等的話,并一定是相等的兩個對象,即equals并一定相等(我的理解是equals比較的是兩個對象,而hasCode()是對象的屬性,對象相等那么其屬性一定相等,相反其屬性相等但是并不一定是同一個對象)。這就要求我們在重寫自定義類的時候如果重寫了equals()方法的話,一定也要重寫hascode()方法。
同時我們知道Set集合是不允許有重復的內容的。具體判斷set集合中是否有已經有該對象的步驟如下:
1)、判斷兩個對象的hashCode是否相等 。
????? 如果不相等,認為兩個對象也不相等,完畢?
????? 如果相等,轉入2)
(這一點只是為了提高存儲效率而要求的,其實理論上沒有也可以,但如果沒有,實際使用時效率會大大降低,所以我們這里將其做為必需的。)?
2)、判斷兩個對象用equals運算是否相等 。
????? 如果不相等,認為兩個對象也不相等?
????? 如果相等,認為兩個對象相等(equals()是判斷兩個對象是否相等的關鍵)
如果想查找一個集合中是否包含有某個對象,大概的程序代碼怎樣寫呢?
你通常是逐一取出每個元素與要查找的對象進行比較,當發現某個元素與要查找的對象進行equals方法比較的結果相等時,則停止繼續查找并返回肯定的信息,否則,返回否定的信息,如果一個集合中有很多個元素,比如有一萬個元素,并且沒有包含要查找的對象時,則意味著你的程序需要從集合中取出一萬個元素進行逐一比較才能得到結論。
有人發明了一種哈希算法來提高從集合中查找元素的效率,這種方式將集合分成若干個存儲區域,每個對象可以計算出一個哈希碼,可以將哈希碼分組(使用不同的hash函數來計算的),每組分別對應某個存儲區域,根據一個對象的哈希嗎就可以確定該對象應該存儲在哪個區域HashSet就是采用哈希算法存取對象的集合,它內部采用對某個數字n進行取余(這種的hash函數是最簡單的)的方式對哈希碼進行分組和劃分對象的存儲區域;Object類中定義了一個hashCode()方法來返回每個Java對象的哈希碼,當從HashSet集合中查找某個對象時,Java系統首先調用對象的hashCode()方法獲得該對象的哈希碼表,然后根據哈希嗎找到相應的存儲區域,最后取得該存儲區域內的每個元素與該對象進行equals方法比較;這樣就不用遍歷集合中的所有元素就可以得到結論,可見,HashSet集合具有很好的對象檢索性能,但是,HashSet集合存儲對象的效率相對要低些,因為向HashSet集合中添加一個對象時,要先計算出對象的哈希碼和根據這個哈希碼確定對象在集合中的存放位置為了保證一個類的實例對象能在HashSet正常存儲,要求這個類的兩個實例對象用equals()方法比較的結果相等時,他們的哈希碼也必須相等;也就是說,如果obj1.equals(obj2)的結果為true,那么以下表達式的結果也要為true:
obj1.hashCode() == obj2.hashCode()
換句話說:當我們重寫一個對象的equals方法,就必須重寫他的hashCode方法,不過不重寫他的hashCode方法的話,Object對象中的hashCode方法始終返回的是一個對象的hash地址,而這個地址是永遠不相等的。所以這時候即使是重寫了equals方法,也不會有特定的效果的,因為hashCode方法如果都不想等的話,就不會調用equals方法進行比較了,所以沒有意義了。
?
如果一個類的hashCode()方法沒有遵循上述要求,那么,當這個類的兩個實例對象用equals()方法比較的結果相等時,他們本來應該無法被同時存儲進set集合中,但是,如果將他們存儲進HashSet集合中時,由于他們的hashCode()方法的返回值不同(Object中的hashCode方法返回值是永遠不同的),就是說可能會被set保存兩次的。
看下面這個例子:
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方法,調用?person1.equals(person2) 也是返回true的,但是最終set.size()是2。這是運行結果
我們再修改一下例子,重寫一下 hascode方法
@Overridepublic int hashCode(){return age + name.hashCode();}
?
再看一下結果
我們再來看一個有意思的事情,如果我們把equals直接返回false,那么再調用set.add(person1),那么按照我們以上的分析,先檢查hashCode() 是否相等,再調用equals()方法,那么這時候應該返回 set.size() 為3,然而事情的真相真如我們分析的那樣么,先看運行結果;
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實現的,我們查看一下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一樣的話,會去判斷 key == key 和 key.equals(k),只要兩個有一個符合要求就插入不成功,而對于同一個對象來說 ?person1 == person1 永遠是true,所以set里面還是2,當然這種情況很少會遇到,一般重寫 equals()方法的時候不會直接返回 false~。如果非要讓set變成3個話也行,重寫一下hashCode就可以了,比如我們可以產生一個隨機數?
@Overridepublic int hashCode(){return (int)(Math.random()*100);}
?最后在看一個刪除時候應該注意的地方:
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());}
?運行結果:
?事情又出乎我們的意料之外,我們明明已經刪除了person1,但是事實上并沒有刪除成功,我在程序中已經把原因打印出出來了,更改了屬性以后,因為我們的hashCode()是根據屬性值來生成的,因此屬性更改以后hashCode()也被修改了但是他的存儲位置不會有變化,當刪除的時候會按照新的hashCode()去找person1,肯定找不到,所以并沒有刪除成功,會導致失敗。
這告訴我們一個事情:如果對象的屬性值參與了hashCode的運算中,在進行刪除的時候,就不能對其屬性值進行修改,否則會出現嚴重的問題。
轉載于:https://www.cnblogs.com/l919310075/p/7376229.html
總結
以上是生活随笔為你收集整理的关于 hashcode 和 equals的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 今生相伴下一句是什么呢?
- 下一篇: RzPageControl 关闭按钮