一個有趣的問題: 如何用HashSet來存儲重複的字符串?

1. 前言

今天,我們來探討一個實際中不常用但卻比較有意思的問題。它能幫助你理解 “HashSet中的鍵值是唯一的,不可重複的” 這句話的真正含義,也考驗你對問題的思考深度。

注:實際應用中,我們一般是用 ArrayList 集合來存儲相同的字符串的,不會用 HashSet 來存。

我們平時都看到或聽說 HashSet 是不能用來存放重複的字符串的,是真的存放不了嗎?如果面試問你這個問題,你能給出解決方案嗎?

2. 參考解答

先給出參考解答,然後我們再來分析爲什麼。

解答:
雖然我們不能用 HashSet 來存放 String 類型重複的字符串,但我們可以用 HashSet 來存儲 StringBuilder 類型重複的字符串呀。

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());

    }
}

輸出結果:

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 類型的重複字符串。

3. 爲什麼?

從打印的 hashCode 來看,String 類型,相同字符串的不同 String 對象哈希值是一樣的。而對於 StringBuilder 類型,相同字符串的不同對象哈希值是不同的。

要知道這個問題的答案,我們首先得了解 Java 虛擬機是如何判斷兩個對象是否相同的。

那 Java 虛擬機是如何判斷兩個對象是否相同的呢?

參考解答:
Java 虛擬機會先判斷兩個對象的 hashCode 是否相同,如果 hashCode 不同,則說明肯定是兩個不同的對象了;如果 hashCode 相同再通過 equals() 方法進行進一步比較,如果 equals 方法返回 true,則說明兩個對象是相同的,如果equals方法返回 false 說明兩個對象不同。

具體驗證思路如果你感興趣,請查看: 你有沒有想過: Java 虛擬機是如何判斷兩個對象是否相同的?判斷的流程是什麼?

那爲什麼相同字符串的不同 String 對象哈希值是一樣的,而且還被虛擬機判斷爲相同的對象了呢?

因爲 String 類複寫了 Object 類的 hashCode() 和 equals() 方法,並實現了自己的 hashCode 值生成算法和 equals 的比較規則,具有相同字符串內容的不同 String 對象在初始化時生成的 hashCode 值是一樣的,並且 String 類 equals() 方法比較的是兩個字符串的內容,而不是內存地址值,這兩個條件同時成立, 這就使 Java 虛擬機把具有相同內容的不同 String 對象判斷爲相同的對象了,就不會存入 HashSet 集合中。

而 StringBuilder 爲什麼就可以呢?它相同內容的不同對象的哈希值值爲什麼是不同的?

查看 StringBuilder 類的源碼你會發現,因爲 StringBuilder 並沒有複寫 Object 類的 hashCode() 方法和 equals() 方法,StringBuilder 用的是父類 Object 類的 hashCode 生成算法,也就是用 native 層的 hashCode 生成算法,很大概率產生的哈希值是不一樣的,即使產生了一樣的哈希值,Object 類的 equals() 方法比較的是兩個對象的內存地址,而不是兩個對象的內容,這就使 Java 虛擬機把具有相同內容的 StringBuilder 對象判斷爲不同的對象,就可以存入 HashSet 集合中了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章