String到底相不相等?String初始化及String.intern()方法淺析

引言

在各種面試題中經(jīng)常見到類似下述的面試題

寫出main方法的打印結(jié)果

    class test{        
        public static void main(String[] args){
            String s1 = new String("1") + new String("1") + new String("1");
            String s2 = new String("11") + new String("1");
            System.out.println(s1 == s2);
            s2.intern();
            String s3 = "111";
            System.out.println(s2 == s3);
            String s4 = s1.intern();
            System.out.println(s4 == s1);
    
            String s6 = new String("1");
            String s7 = "1";
            String s8 = s6.intern();
            System.out.println(s7 == s6);
            System.out.println(s8 == s6);
            System.out.println(s7 == s8);
        }
    }

答案為:

false
true
false
false
false
true
false
false
true

此題要求對String類的引用有相關(guān)了解,提煉相關(guān)的要點(diǎn)如下

要點(diǎn)列表

  1. 兩個(gè)對象obj1和obj2,當(dāng)且僅當(dāng)obj1與obj2指向相同的引用(內(nèi)存地址,對象地址)時(shí),ojb1==obj2返回true,其他返回false
  2. String初始化時(shí),若采用String s = "1";這種字面量直接賦值的形式,則過程為:
    判斷字符串常量池中是否有值與字面量相同的引用,有的話s指向這個(gè)引用指向的對象,否則在堆上新建值為字面量的String對象,s指向新建的對象,并在字符串常量池中存儲(chǔ)對應(yīng)的引用
  3. String初始化時(shí),若采用String s = new String("1");這種new對象的形式,或者使用其他帶參構(gòu)造器,則過程為:
    在堆上新建字符串實(shí)例,值為相關(guān)值,s直接指向堆上新增的這個(gè)對象
  4. String s = new String("1") + new String("1")等同于String s = new StringBuilder().append("1").append("1").toString();由于StringBuilder的toString方法也是調(diào)用的String的帶參構(gòu)造方法,
    因此在引用處理時(shí),結(jié)果與3相同
  5. String類的intern()方法執(zhí)行過程為:檢查字符串常量區(qū)是否有相同值(hash)的引用,有的話返回此引用,否則將調(diào)用者放入字符串常量區(qū),并返回調(diào)用者的引用

實(shí)際代碼分析

如下為上述代碼及每一行的分析

    String s1 = new String("1") + new String("1") + new String("1");
                                    ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
        上面這段代碼,在運(yùn)行時(shí)會(huì)在堆上創(chuàng)建對象"1"并在字符串常量池中創(chuàng)建對應(yīng)的引用,
        同時(shí)由于字符串常量池中沒有值為"111"的對象,因此會(huì)在堆上創(chuàng)建一個(gè)值為"111"的對象,
        s1指向剛創(chuàng)建的這個(gè)"111"對象,但不會(huì)在常量池中新創(chuàng)建值為"111"的引用,因此常量池中仍舊
        沒有值為"111"的對象

                                   ↓↓↓↓↓↓↓↓內(nèi)存狀態(tài)↓↓↓↓↓↓↓↓↓
        --------------------------------------堆--------------------------------------------
        addr1("1")
        addr2("111")
        ------------------------------------------------------------------------------------

        ---------------------------------字符串常量池---------------------------------------
        addr1("1")
        ------------------------------------------------------------------------------------

        ---------------------------------------變量-----------------------------------------
        s1 -> addr2
        ------------------------------------------------------------------------------------
        String s2 = new String("11") + new String("1");
    /*
                                    ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
        上面這段代碼,在運(yùn)行時(shí)會(huì)在堆上創(chuàng)建對象"11"并在字符串常量池中創(chuàng)建對應(yīng)的引用,
        同時(shí)由于字符串常量池中仍舊沒有值為"111"的對象,因此會(huì)在堆上創(chuàng)建一個(gè)值為"111"的對象,
        s2指向剛創(chuàng)建的這個(gè)"111"對象,
        注意,此"111"對象地址與s1不同,此時(shí)在堆上存在兩個(gè)值都為"111"的String對象

                                   ↓↓↓↓↓↓↓↓內(nèi)存狀態(tài)↓↓↓↓↓↓↓↓↓
        --------------------------------------堆--------------------------------------------
        addr1("1")
        addr2("111")
        addr3("11")
        addr4("111")
        ------------------------------------------------------------------------------------

        ---------------------------------字符串常量池---------------------------------------
        addr1("1")
        addr3("11")
        ------------------------------------------------------------------------------------

        ---------------------------------------變量-----------------------------------------
        s1 -> addr2
        s2 -> addr4
        ------------------------------------------------------------------------------------
        System.out.println(s1 == s2);
    /*
                                    ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
        根據(jù)上面的運(yùn)行過程可知,s1和s2分別指向堆上兩個(gè)對象,只是堆上的對象恰巧值均為"111",
        因此打印false
     */
    s2.intern();
    /*
                                    ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
        根據(jù)JDK7以后的實(shí)現(xiàn),上面這句代碼會(huì)檢查字符串常量區(qū),此時(shí)字符串常量區(qū)中沒有值為"111"
        的引用,因此,會(huì)將s2的引用復(fù)制到字符串常量區(qū)

                                   ↓↓↓↓↓↓↓↓內(nèi)存狀態(tài)↓↓↓↓↓↓↓↓↓
        --------------------------------------堆--------------------------------------------
        addr1("1")
        addr2("111")
        addr3("11")
        addr4("111")
        ------------------------------------------------------------------------------------

        ---------------------------------字符串常量池---------------------------------------
        addr1("1")
        addr3("11")
        addr4("111")
        ------------------------------------------------------------------------------------

        ---------------------------------------變量-----------------------------------------
        s1 -> addr2
        s2 -> addr4
        ------------------------------------------------------------------------------------
     */
        String s3 = "111";
    /*
                                    ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
        執(zhí)行了上述intern()過程后,字符串常量區(qū)中已有值為"111"的引用,根據(jù)實(shí)現(xiàn),s3賦值為常量
        區(qū)中值為"111"對應(yīng)的引用也就是s2指向的堆上那個(gè)對象的地址

                                   ↓↓↓↓↓↓↓↓內(nèi)存狀態(tài)↓↓↓↓↓↓↓↓↓
        --------------------------------------堆--------------------------------------------
        addr1("1")
        addr2("111")
        addr3("11")
        addr4("111")
        ------------------------------------------------------------------------------------

        ---------------------------------字符串常量池---------------------------------------
        addr1("1")
        addr3("11")
        addr4("111")
        ------------------------------------------------------------------------------------

        ---------------------------------------變量-----------------------------------------
        s1 -> addr2
        s2 -> addr4
        s3 -> addr4
        ------------------------------------------------------------------------------------
     */
        System.out.println(s2 == s3);
    /*
                                    ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
        s3和s2指向相同的堆上的對象,因此結(jié)果為true
     */
        String s4 = s1.intern();
    /*
                                    ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
        因?yàn)樽址A繀^(qū)中已有值為"111"的引用,因此此方法不會(huì)對字符串常量區(qū)中的值產(chǎn)生影響,
        但因?yàn)閕nter()方法返回的是字符串常量區(qū)中的引用,因此s4指向s2對應(yīng)的對象

                                   ↓↓↓↓↓↓↓↓內(nèi)存狀態(tài)↓↓↓↓↓↓↓↓↓
        --------------------------------------堆--------------------------------------------
        addr1("1")
        addr2("111")
        addr3("11")
        addr4("111")
        ------------------------------------------------------------------------------------

        ---------------------------------字符串常量池---------------------------------------
        addr1("1")
        addr3("11")
        addr4("111")
        ------------------------------------------------------------------------------------

        ---------------------------------------變量-----------------------------------------
        s1 -> addr2
        s2 -> addr4
        s3 -> addr4
        s4 -> addr4
        ------------------------------------------------------------------------------------
     */
        System.out.println(s4 == s1);
    /*
                                    ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
        s4和s1指向堆上的不同的對象,因此結(jié)果為false
     */
        String s6 = new String("1");
        String s7 = "1";
        String s8 = s6.intern();
    /*
                                    ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
        根據(jù)上面的內(nèi)存狀態(tài)可知,s6仍舊指向堆上一個(gè)新建的值為"1"的對象,
        s6.inter()返回的是字符串常量區(qū)已有的值為"1"的對象地址,
        s8也等于這個(gè)地址

                                   ↓↓↓↓↓↓↓↓內(nèi)存狀態(tài)↓↓↓↓↓↓↓↓↓
        --------------------------------------堆--------------------------------------------
        addr1("1")
        addr2("111")
        addr3("11")
        addr4("111")
        addr5("1")
        ------------------------------------------------------------------------------------

        ---------------------------------字符串常量池---------------------------------------
        addr1("1")
        addr3("11")
        addr4("111")
        ------------------------------------------------------------------------------------

        ---------------------------------------變量-----------------------------------------
        s1 -> addr2
        s2 -> addr4
        s3 -> addr4
        s4 -> addr4
        s6 -> addr5
        s7 -> addr1
        s8 -> addr1
        ------------------------------------------------------------------------------------


     */
        System.out.println(s7 == s6);
        System.out.println(s8 == s6);
        System.out.println(s7 == s8);
    /*
        根據(jù)上面的內(nèi)存狀態(tài)可知,打印結(jié)果為:
        false
        false
        true
     */

延申

  1. 由于使用有參構(gòu)造器來初始化變量時(shí),總會(huì)在堆上新建變量,因此在極限情況下,確有可能在初始化階段造成OOM,解決方法是盡量使用字面量初始化字符串
  2. String內(nèi)部字符串常量區(qū)的實(shí)現(xiàn)方式不同,openjdk中,實(shí)現(xiàn)類似HashMap,當(dāng)放入過多常量時(shí),插入與查找也會(huì)產(chǎn)生部分性能損耗,因此,調(diào)用String的intern()方法也要看情況確定
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容