
String s=new String("abc")建立了幾個對象?
本文将圍繞字元串常量池與字元串展開介紹。
字元串常量池和運作時常量池是兩個不同的概念。運作時常量池存儲的是類的字面量,是每個類獨有的,而字元串常量池存儲的是字元串字面量,是所有類共享的。
JDK1.7字元串常量池在方法區,JDK1.7之後字元串常量池就轉移到了堆區。
String s="abc"會先從字元串常量池(下文簡稱常量池)中查找,如果常量池中已經存在"abc",而"abc"必定指向堆區的某個String對象,那麼直接将s指向這個String對象即可;如果常量池中不存在"abc",則在堆區new一個String對象,然後将"abc"放到常量池中,并将"abc"指向剛new好的String對象。
new String("abc")相當于new String(String s1="abc"),即先要執行String s1="abc"(2.1已經講過了),然後再在堆區new一個String對象。
是以,現在可以解答本文的标題了,String s=new String("abc")建立了1或2個對象,String s="abc"建立了0或1個對象。
下面介紹一個方法,String類的intern方法,現有如下代碼。
首先會檢查常量池中是否存在"abc",如果存在,則s.intern()會傳回"abc"指向堆區的對象,如果不存在,則将"abc"放入常量池,并将"abc"指向s指向的對象。
答案是會,如何驗證呢?
下面用反證法證明。
如果不會,那麼是.intern()執行之後,常量池中"abc"指向的就是s1指向的對象,s也是指向常量池中"abc"指向的對象,那麼s和s1指向的是同一對象,結果就是true,但結果是false,說明會放到常量池。
首先來看String s="hello"+"world"這句代碼。
通過javap -c class檔案名稱可以檢視位元組碼,如下圖所示。
可以看到String s="hello"+"world"與String s="helloworld"是一樣的。
再看下面一段代碼
通過javap指令檢視位元組碼,如下圖所示。
第一步:new一個StringBuilder;
第二步:append s1;
第三步:append s2;
第四步:調用StringBuilder的toString方法;
這下我們知道了,+操作符的原理是StringBuilder的append方法。
下面再看一道經典面試題,如下代碼所示。
這道題的疑惑點在于:s1+s2拼接之後的"ab"會不會放進常量池裡,如果會,那麼s3==s4傳回true,如果不會,則傳回false。
我們已經知道s1+s2會通過StringBuilder的append方法進行拼接,然後通過StringBuilder的toString方法傳回"ab",那我們看一下toString方法的源碼,如下圖所示。
這下我們知道了,這是new了一個String對象,并且new好的String對象不會放進常量池裡,是以s3==s4傳回false。
我們知道,用+操作符拼接字元串,會産生中間對象,如果是線程安全的環境,我們會用StringBuffer拼接字元串,線程不安全的環境則使用StringBuilder。
産生中間對象的原因是String是不可變的,我們看看String類的源碼,如下圖所示。
String類用聲明為final的char數組來存儲字元串,在拼接的時候由于char數組不可變,是以會産生中間對象;
那到底産生的是哪些中間對象呢?其實上面已經講到了,+操作符拼接字元串時,每次會new一個StringBuilder對象,然後将+操作符連接配接的兩個字元串append上,最後toString,而toString又會new一個String對象,是以每使用一次+操作符都會産生2個中間對象。
String類的replace方法隻是替換原來的值,是以不會産生中間對象;
那StringBuilder和StringBuffer在拼接字元串時為什麼效率高呢?StringBuilder和StringBuffer都繼承自AbstractStringBuilder,AbstractStringBuilder的源碼如下圖所示。
可以看到AbstractStringBuilder中的char數組沒有聲明為final,是以是可變的。