|
轉(zhuǎn):https://www.jianshu.com/p/55626bec6b89 在java 中Object是一個(gè)具體的類,但是他的設(shè)計(jì)主要是為了拓展。當(dāng)我們寫一個(gè)類的時(shí)候,都會(huì)對(duì)Java.lang.Object類的一些重要方法進(jìn)行重寫( 改寫 override),這些方法包含:hashCode(),toString(),equals(),finalize(),clone(),wait(),notify()/notifyAll() 這八個(gè)方法。 這里將總結(jié)介紹這些基本的方法,并對(duì)這些方法的改寫提供總結(jié)。 1 equals方法改寫java中==和eqauls()的區(qū)別==是運(yùn)算符,用于比較兩個(gè)變量是否相等,而equals是Object類的方法,用于比較兩個(gè)對(duì)象是否相等。默認(rèn)Object類的equals方法是比較兩個(gè)對(duì)象的地址,此時(shí)和==的結(jié)果一樣。換句話說:基本類型比較用==,比較的是他們的值。默認(rèn)下,對(duì)象用==比較時(shí),比較的是內(nèi)存地址,如果需要比較對(duì)象內(nèi)容,需要重寫equal方法。 下面來理解一下:如果需要比較對(duì)象內(nèi)容,需要重寫equal方法。 

可以看到Object的equals方法實(shí)現(xiàn),默認(rèn)Object類的equals方法是比較兩個(gè)對(duì)象的地址,此時(shí)和==的結(jié)果一樣。 String 進(jìn)行了重寫從而實(shí)現(xiàn)了字符串的比較。那么下方這里字符串比較肯定返回true了。 public static void main(String[] args) { String str1 = new String("123"); String str2 = new String("123");
System.out.println(str1.equals(str2)); //true
}
實(shí)現(xiàn)高質(zhì)量的equals方法的訣竅包括使用==操作符檢查“參數(shù)是否為這個(gè)對(duì)象的引用”; 使用instanceof操作符檢查“參數(shù)是否為正確的類型”; 對(duì)于類中的關(guān)鍵屬性,檢查參數(shù)傳入對(duì)象的屬性是否與之相匹配; 編寫完equals方法后,問自己它是否滿足對(duì)稱性、傳遞性、一致性; 重寫equals時(shí)總是要重寫hashCode; 不要將equals方法參數(shù)中的Object對(duì)象替換為其他的類型,在重寫時(shí)不要忘掉@Override注解。
理解這幾個(gè)步驟,直接參考源代碼Set,list和map的父類的源代碼的equals方法的比較: 


重寫equals 應(yīng)該遵守的約定自反性(x.equals(x)必須返回true); 對(duì)稱性(x.equals(y)返回true時(shí),y.equals(x)也必須返回true); 傳遞性(x.equals(y)和y.equals(z)都返回true時(shí),x.equals(z)也必須返回true); 一致性(當(dāng)x和y引用的對(duì)象信息沒有被修改時(shí),多次調(diào)用x.equals(y)應(yīng)該得到同樣的返回值); 非空性(對(duì)于任何非null值的引用x,x.equals(null)必須返回false)。 2 hashCode方法改寫此方法返回對(duì)象的哈希碼值,什么是哈希碼?哈希碼產(chǎn)生的依據(jù):哈希碼并不是完全唯一的,它是一種算法,讓同一個(gè)類的對(duì)象按照自己不同的特征盡量的有不同的哈希碼,但不表示不同的對(duì)象哈希碼完全不同。也有相同的情況,看程序員如何寫哈希碼的算法。
簡(jiǎn)單理解就是一套算法算出來的一個(gè)值,且這個(gè)值對(duì)于這個(gè)對(duì)象相對(duì)唯一。哈希算法有一個(gè)協(xié)定:在 Java 應(yīng)用程序執(zhí)行期間,在對(duì)同一對(duì)象多次調(diào)用 hashCode 方法時(shí),必須一致地返回相同的整數(shù),前提是將對(duì)象進(jìn)行hashcode比較時(shí)所用的信息沒有被修改。(ps:要是每次都返回不一樣的,就沒法玩兒了) 兩個(gè)對(duì)象值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對(duì)不對(duì)?不對(duì),如果兩個(gè)對(duì)象x和y滿足x.equals(y) == true,它們的哈希碼(hash code)應(yīng)當(dāng)相同。Java對(duì)于eqauls方法和hashCode方法是這樣規(guī)定的:(1)如果兩個(gè)對(duì)象相同(equals方法返回true),那么它們的hashCode值一定要相同;(2)如果兩個(gè)對(duì)象的hashCode相同,它們并不一定相同。當(dāng)然,你未必要按照要求去做,但是如果你違背了上述原則就會(huì)發(fā)現(xiàn)在使用容器時(shí),相同的對(duì)象可以出現(xiàn)在Set集合中,同時(shí)增加新元素的效率會(huì)大大下降(對(duì)于使用哈希存儲(chǔ)的系統(tǒng),如果哈希碼頻繁的沖突將會(huì)造成存取性能急劇下降) 可以在hashcode中使用隨機(jī)數(shù)字嗎?不行,因?yàn)橥粚?duì)象的 hashcode 值必須是相同的 重寫equals方法的時(shí)候?yàn)槭裁葱枰貙慼ashcode首先來看一段代碼: public class HashMapTest {
private int a; public HashMapTest(int a) { this.a = a;
} public static void main(String[] args) {
Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
HashMapTest instance = new HashMapTest(1); map.put(instance, 1);
Integer value = map.get(new HashMapTest(1)); if (value != null) {
System.out.println(value);
} else {
System.out.println("value is null");
}
}
}//程序運(yùn)行結(jié)果: value is null
簡(jiǎn)單說下HashMap的原理,HashMap存儲(chǔ)數(shù)據(jù)的時(shí)候,是取的key值的哈希值,然后計(jì)算數(shù)組下標(biāo),采用鏈地址法解決沖突,然后進(jìn)行存儲(chǔ);取數(shù)據(jù)的時(shí)候,依然是先要獲取到hash值,找到數(shù)組下標(biāo),然后for遍歷鏈表集合,進(jìn)行比較是否有對(duì)應(yīng)的key。比較關(guān)心的有2點(diǎn):1.不管是put還是get的時(shí)候,都需要得到key的哈希值,去定位key的數(shù)組下標(biāo); 2.在get的時(shí)候,需要調(diào)用equals方法比較是否有相等的key存儲(chǔ)過。 ??反過來,我們?cè)俜治錾厦婺嵌未a,Map的key是我們自己定義的一個(gè)類,可以看到,我們沒有重寫equal方法,更沒重寫hashCode方法,意思是map在進(jìn)行存儲(chǔ)的時(shí)候是調(diào)用的Object類中equals()和hashCode()方法。為了證實(shí),我們打印下hashCode碼。 public class HashMapTest {
private Integer a; public HashMapTest(int a) { this.a = a;
} public static void main(String[] args) {
Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
HashMapTest instance = new HashMapTest(1);
System.out.println("instance.hashcode:" + instance.hashCode()); map.put(instance, 1);
HashMapTest newInstance = new HashMapTest(1);
System.out.println("newInstance.hashcode:" + newInstance.hashCode());
Integer value = map.get(newInstance); if (value != null) {
System.out.println(value);
} else {
System.out.println("value is null");
}
}
}//運(yùn)行結(jié)果://instance.hashcode:929338653//newInstance.hashcode:1259475182//value is null
不出所料,hashCode不一致,所以對(duì)于為什么拿不到數(shù)據(jù)就很清楚了。這2個(gè)key,在Map計(jì)算的時(shí)候,可能數(shù)組下標(biāo)就不一致,就算數(shù)據(jù)下標(biāo)碰巧一致,根據(jù)前面,最后equals比較的時(shí)候也不可能相等(很顯然,這是2個(gè)對(duì)象,在堆上的地址必定不一樣)。我們繼續(xù)往下看,假如我們重寫了equals方法,將這2個(gè)對(duì)象都put進(jìn)去,根據(jù)map的原理,只要是key一樣,后面的值會(huì)替換前面的值,接下來我們實(shí)驗(yàn)下: public class HashMapTest { private Integer a; public HashMapTest(int a) { this.a = a;
} public static void main(String[] args) {
Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
HashMapTest instance = new HashMapTest(1);
HashMapTest newInstance = new HashMapTest(1);
map.put(instance, 1);
map.put(newInstance, 2);
Integer value = map.get(instance);
System.out.println("instance value:"+value);
Integer value1 = map.get(newInstance);
System.out.println("newInstance value:"+value1);
} public boolean equals(Object o) { if(o == this) { return true;
} else if(!(o instanceof HashMapTest)) { return false;
} else {
HashMapTest other = (HashMapTest)o; if(!other.canEqual(this)) { return false;
} else {
Integer this$data = this.getA();
Integer other$data = other.getA(); if(this$data == null) { if(other$data != null) { return false;
}
} else if(!this$data.equals(other$data)) { return false;
} return true;
}
}
} protected boolean canEqual(Object other) { return other instanceof HashMapTest;
} public void setA(Integer a) { this.a = a;
} public Integer getA() { return a;
}
}//運(yùn)行結(jié)果://instance value:1//newInstance value:2
你會(huì)發(fā)現(xiàn),不對(duì)呀?同樣的一個(gè)對(duì)象,為什么在map中存了2份,map的key值不是不能重復(fù)的么?沒錯(cuò),它就是存的2份,只不過在它看來,這2個(gè)的key是不一樣的,因?yàn)樗麄兊墓4a就是不一樣的,可以自己測(cè)試下,上面打印的hash碼確實(shí)不一樣。那怎么辦?只有重寫hashCode()方法,更改后的代碼如下: public class HashMapTest { private Integer a; public HashMapTest(int a) { this.a = a;
} public static void main(String[] args) {
Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
HashMapTest instance = new HashMapTest(1);
System.out.println("instance.hashcode:" + instance.hashCode());
HashMapTest newInstance = new HashMapTest(1);
System.out.println("newInstance.hashcode:" + newInstance.hashCode());
map.put(instance, 1);
map.put(newInstance, 2);
Integer value = map.get(instance);
System.out.println("instance value:"+value);
Integer value1 = map.get(newInstance);
System.out.println("newInstance value:"+value1);
} public boolean equals(Object o) { if(o == this) { return true;
} else if(!(o instanceof HashMapTest)) { return false;
} else {
HashMapTest other = (HashMapTest)o; if(!other.canEqual(this)) { return false;
} else {
Integer this$data = this.getA();
Integer other$data = other.getA(); if(this$data == null) { if(other$data != null) { return false;
}
} else if(!this$data.equals(other$data)) { return false;
} return true;
}
}
} protected boolean canEqual(Object other) { return other instanceof HashMapTest;
} public void setA(Integer a) { this.a = a;
} public Integer getA() { return a;
} public int hashCode() { boolean PRIME = true; byte result = 1;
Integer $data = this.getA(); int result1 = result * 59 + ($data == null?43:$data.hashCode()); return result1;
}
}//運(yùn)行結(jié)果://instance.hashcode:60//newInstance.hashcode:60//instance value:2//newInstance value:2
可以看到,他們的hash碼是一致的,且最后的結(jié)果也是預(yù)期的。 曾經(jīng)同事趟過這坑,Map中存了2個(gè)數(shù)值一樣的key,所以大家謹(jǐn)記喲! 在重寫equals方法的時(shí)候,一定要重寫hashCode方法。 作者:安東尼_Anthony 鏈接:https://www.jianshu.com/p/55626bec6b89 來源:簡(jiǎn)書 簡(jiǎn)書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。
|