理解Java中的hashCode和equals 方法
Java代碼
````
1,clone()
2,equals(Object obj)
3,finalize()
4,getClass()
5,hashCode()
6,notify()
7,notifyAll()
8,toString()
9,wait()
10,wait(long timeout)
11,wait(long timeout, int nanos)
````
這裏面我們常用的方法有三個:
Java代碼
````
toString()
equals(Object obj)
hashCode()
````
toString方法,相信用過Java的人都不會陌生,默認打印的是:類名@十六進制的hashCode,源碼中定義如下:
Java代碼
````
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
````
在經過重寫後,我們可以打印一個class的所有屬性,這樣在打印log或調試時比較方便。
下面重點介紹下hashCode和equals方法:
(1)equals方法,在JDK默認的情況下比較的是對象的內存地址,源碼如下:
Java代碼
````
public boolean equals(Object obj) {
return (this == obj);
}
````
(2)hashcode方法,默認情況下返回的是一個唯一的整數,代表該實例的內存地址,註意這個數字
並不是實際的內存地址,Java是沒辦法直接獲取內存地址的,必須得由C或者C++獲取,所以這個方法是用
native修飾的
Java代碼
````
public native int hashCode();
````
由於默認情況下,equals方法比較的是內存地址,而在實際開發中,我們判斷兩個對象是否相等,一般都是根據對象的屬性來判斷的,
所以需要重寫這個方法,不然的話,是沒辦法比較的。舉例如下:
定義的類如下:
Java代碼
````
public class Hero {
private String id;
private String name;
public Hero() {
}
public Hero(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
````
直接比較兩個對象,結果是不相等的:
Java代碼
````
`````` Hero h1=new Hero("1","張飛");
Hero h2=new Hero("1","張飛");
//false
System.out.println(h1.equals(h2));
````
因為他們的內存地址是不同的,所以結果是false,如果我們想要認為他是相等的,那麽就需要重寫
equals方法:
Java代碼
````
@Override
public boolean equals(Object o) {
if (this == o) return true;//如果內存地址相等,則兩個對象必定相等
if (o == null || getClass() != o.getClass()) return false;//如果有一個為null或者class不一樣,認為必不相等
Hero hero = (Hero) o;//
if (id != null ? !id.equals(hero.id) : hero.id != null) return false;//比較id,不相等就返回false
return name != null ? name.equals(hero.name) : hero.name == null;//上面的條件通過後比較name,如果相等返回true,不相等返回false
}
````
在重寫equals方法後,我們在比較兩個對象,發現就相等了
Java代碼
````
``` Hero h1=new Hero("1","張飛");
Hero h2=new Hero("1","張飛");
//true
System.out.println(h1.equals(h2));
````
接著我們看第二個例子,將其放入ArrayList中,然後判斷是否存在,發現也生效了:
Java代碼
````
` Hero h1=new Hero("1","張飛");
List<Hero> heros=new ArrayList<Hero>();
heros.add(h1);
//true
System.out.println(heros.contains(new Hero("1","張飛")));
````
到目前為止,我們還沒有對hashCode進行操作,那麽大家可能會有一個疑問,既然都有equals方法比較了,為啥還需要hashCode方法呢? 別著急,繼續看下面的例子:
我們都知道在Java裏面HashSet類,去無序去重的,下面看一下,只重寫equasl方法能不能實現對class的去重:
Java代碼
````
` Hero h1=new Hero("1","張飛");
Hero h2=new Hero("1","張飛");
Set<Hero> heros=new HashSet<Hero>();
heros.add(h1);
heros.add(h2);
//2
System.out.println(heros.size());
//false
System.out.println(heros.contains(new Hero("1","張飛")));
````
從上面的結果看,並沒有去重,有的小夥伴會說為啥時string類型的時候就能去重?這是因為Stirng類默認已經重寫了equals和hashcode方法,當然所有的基本類型都重寫這兩個方法了。
接著回到上面的問題,為什麽在HashSet中去重失效了呢?
其實,不止是HashSet,在HashMap和Hashtable等等所有使用hash相關的數據結構中,如果使用時不重寫hashcode,那麽就沒法比較對象是否存在。
這其實與HashMap存儲原理相關(HashSet底層用的也是HashMap),HashMap在存儲時其實是采用了數組+鏈表的存儲結構,數組
中的每一個元素,我們可以理解成是一個buckets(桶),桶裏面的結構是鏈表,而數據是如何分到各個桶裏面其實與hashCode有很大關系,只有hashCode一樣的
對象才能被分到一個桶裏。存的時候,遍歷鏈表判斷是否存在,如果存在就不覆蓋,如果不存在就把該元素放在鏈表的頭,把next指向上一次的頭,而讀取的時候先定位到桶裏,然後遍歷
鏈表找到該元素即可。
理解了這些,就明白了為啥上面的例子中,去重失效了。就是因為他們的hashCode不一樣,導致被分到不同的桶裏面了,自然就沒法去重了。
重寫hashCode之後,再看結果:
Java代碼
````
@Override
public int hashCode() {
return Integer.parseInt(id);
}
````
Java代碼
````
· Hero h1=new Hero("1","張飛");
Hero h2=new Hero("1","張飛");
Set<Hero> heros=new HashSet<Hero>();
heros.add(h1);
heros.add(h2);
//1
System.out.println(heros.size());
//true
System.out.println(heros.contains(new Hero("1","張飛")));
````
這下結果就對了。
那麽問題來了,為啥需要hashCode? 因為在HashSet中,可以存儲大量的元素,如果沒有hashCode,那麽每次就得全量的比較每一個元素,來判斷
是否存在,這樣以來效率肯定極低,而有了hashCode之後,只需要找到該數據的鏈表,然後遍歷這個鏈表的數據即可,這樣以來效率
就大大提升。
一個Java交流平臺分享給你們,讓你在實踐中積累經驗掌握原理。如果你想拿高薪,想突破瓶頸,想跟別人競爭能取得優勢的,想進BAT但是有擔心面試不過的,可以加Java學習交流群:642830685
註:加群要求
1、大學學習的是Java相關專業,畢業後面試受挫,找不到對口工作
2、在公司待久了,現在過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的
3、參加過線下培訓後,知識點掌握不夠深刻,就業困難,想繼續深造
4、已經在Java相關部門上班的在職人員,對自身職業規劃不清晰,混日子的
5、有一定的C語言基礎,接觸過java開發,想轉行的
總結:
(1)如果兩個對象相等,那麽他們必定有相同的hashcode
(2)如果兩個對象的hashcode相等,他們卻不一定相等
(3)重寫equasl方法時,一定要記得重寫hashcode方法,尤其用在hash類的數據結構中。
理解Java中的hashCode和equals 方法