今天,咱們來探討一個實際中不經常使用但卻比較有意思的問題。它能幫助你理解 「HashSet中的鍵值是惟一的,不可重複的」 這句話的真正含義,也考驗你對問題的思考深度。java
注:實際應用中,咱們通常是用 ArrayList 集合來存儲相同的字符串的,不會用 HashSet 來存。面試
咱們平時都看到或據說 HashSet 是不能用來存放重複的字符串的,是真的存放不了嗎?若是面試問你這個問題,你能給出解決方案嗎?算法
先給出參考解答,而後咱們再來分析爲何。post
解答: 雖然咱們不能用 HashSet 來存放 String 類型重複的字符串,但咱們能夠用 HashSet 來存儲 StringBuilder 類型重複的字符串呀。ui
public class HashSetTest {
public static void main(String[] args){
// 用 HashSet 來存放 String 類型的重複的字符串會發生什麼?
HashSet<String> hs1 = new HashSet<>();
String s1 = new String("aaa");
String s2 = new String("aaa");
String s3 = new String("aaa");
hs1.add(s1);
hs1.add(s2);
hs1.add(s3);
System.out.println("hs1:"+hs1); // 重複的字符串是存不進去的
// 用 HashSet 來存放 StringBuilder 類型的重複的字符串又會發生什麼?
HashSet<StringBuilder> hs2 = new HashSet<>();
StringBuilder sb1 = new StringBuilder("aaa");
StringBuilder sb2 = new StringBuilder("aaa");
StringBuilder sb3 = new StringBuilder("aaa");
hs2.add(sb1);
hs2.add(sb2);
hs2.add(sb3);
System.out.println("hs2:"+hs2); // 咦,結果發現重複的字符串也能存進去了
// 那爲何呢?咱們來打印一個各個對象的hashCode看一下
System.out.println("s1的hashCode:"+s1.hashCode());
System.out.println("s2的hashCode:"+s2.hashCode());
System.out.println("s3的hashCode:"+s3.hashCode());
System.out.println("sb1的hashCode:"+sb1.hashCode());
System.out.println("sb2的hashCode:"+sb2.hashCode());
System.out.println("sb3的hashCode:"+sb3.hashCode());
}
}
複製代碼
輸出結果:spa
hs1:[aaa]
hs2:[aaa, aaa, aaa]
s1的hashCode:96321
s2的hashCode:96321
s3的hashCode:96321
sb1的hashCode:356573597
sb2的hashCode:1735600054
sb3的hashCode:21685669
複製代碼
從打印結果來看,咱們是不能用 HashSet 來存放 String 類型的重複字符串的(如hs1),但咱們是能夠用HashSet來存放 StringBuilder 類型的重複字符串。code
從打印的 hashCode 來看,String 類型,相同字符串的不一樣 String 對象哈希值是同樣的。而對於 StringBuilder 類型,相同字符串的不一樣對象哈希值是不一樣的。對象
要知道這個問題的答案,咱們首先得了解 JDK 是如何判斷兩個對象是否相同的。內存
參考解答: JDK 會先判斷兩個對象的 hashCode 是否相同,若是 hashCode 不一樣,則說明確定是兩個不一樣的對象了;若是 hashCode 相同再經過 equals() 方法進行進一步比較,若是 equals 方法返回 true,則說明兩個對象是相同的,若是equals方法返回 false 說明兩個對象不一樣。字符串
具體驗證思路若是你感興趣,請查看: JDK 是如何判斷兩個對象是否相同的?判斷的流程是什麼?
由於 String 類複寫了 Object 類的 hashCode() 和 equals() 方法,並實現了本身的 hashCode 值生成算法和 equals 的比較規則,具備相同字符串內容的不一樣 String 對象在初始化時生成的 hashCode 值是同樣的,而且 String 類 equals() 方法比較的是兩個字符串的內容,而不是內存地址值,這兩個條件同時成立, 這就使 JDK 把具備相同內容的不一樣 String 對象判斷爲相同的對象了,就不會存入 HashSet 集合中。
查看 StringBuilder 類的源碼你會發現,由於 StringBuilder 並無複寫 Object 類的 hashCode() 方法和 equals() 方法,StringBuilder 用的是父類 Object 類的 hashCode 生成算法,也就是用 native 層的 hashCode 生成算法,很大機率產生的哈希值是不同的,即便產生了同樣的哈希值,Object 類的 equals() 方法比較的是兩個對象的內存地址,而不是兩個對象的內容,這就使 JDK 把具備相同內容的 StringBuilder 對象判斷爲不一樣的對象,就能夠存入 HashSet 集合中了。