在一些老套的筆試題中,會要你判斷s1==s2爲false仍是true,s1.equals(s2)爲false仍是true。java
String s1 = new String("xyz"); String s2 = "xyz"; System.out.println(s1 == s2); System.out.println(s1.equals(s2));
對於這種題,你總能很快的給出標準答案:==比較的是對象地址,equals方法比較的是真正的字符數組。因此輸出的是false和true。程序員
上面的屬於最低階的題目,沒有什麼難度。面試
如今這種老套的題目已經慢慢消失了,取而代之的是有一些變形的新題目:數組
String s1 = "aa"; String s2 = "bb"; String str1 = s1 + s2; String str2 = "aabb"; //輸出什麼呢??? System.out.println(str1 == str2); final String s3 = "cc"; final String s4 = "dd"; String str3 = s3 + s4; String str4 = "ccdd"; //又輸出什麼呢??? System.out.println(str3 == str4);
難度提高了一些,但思考一下也不可貴出答案是false和true。微信
今天的文章就是以這幾個題目展開的。app
先簡單看一下String類的結構:優化
能夠發現,String裏面有一個value屬性,是真正存儲字符的char數組。ui
在執行String s = "xyz";
的時候,在堆區建立了一個String對象,一個char數組對象。設計
如何證實建立了一個String對象和一個char數組對象呢?咱們能夠經過IDEA的Debug功能驗證:3d
注意看我截圖的位置,在執行完String s = "xyz";
以後,再次點擊load classes,Diff欄的String和char[]分別加了1,表示在內存中新增了一個char數組對象和一個String對象。
如今,咱們再來看String s = new String("xyz");
建立了幾個對象。
從這張Debug動圖中,咱們能夠得出在String s = new String("xyz");
以後,建立了兩個String對象和一個char數組對象。
又由於String s = new String("xyz");
的s
引用只能指向一個對象,能夠畫出內存分佈圖:
從圖中能夠看到,在堆區,有兩個String對象,這兩個String對象的value都指向同一個char數組對象。
那麼問題來了,下面的那個String對象根本就沒被引用,也就是說他沒有被用到,那麼它究竟是幹什麼的呢?
佔了內存空間又不使用,難道這是JDK的設計缺陷?
很顯然不是JDK的缺陷,JDK雖然確實有設計缺陷,但不至於這麼明顯,這麼愚蠢。
那下面的那個String對象是幹什麼的呢?
答案是用於駐留到字符串常量池中去的,注意,這裏我用了一個駐留
,並非直接把對象放到字符串常量池裏面去,有什麼區別咱們後面再講。
這裏出現了字符串常量池
的概念,我在String s = new String("xyz")建立了幾個實例你真的能答對嗎?中也有過比較詳細的介紹,有興趣的能夠去看一下,這裏再也不重複了。
你只須要知道,字符串常量池在JVM源碼中對應的類是StringTable,底層實現是一個Hashtable。
咱們以String s = new String("xyz");
爲例:
首先去找字符串常量池找,看能不能找到「xyz」字符串對應對象的引用,若是字符串常量池中找不到:
若是字符串常量池中能找到:
而String s = "xyz";
是怎麼樣的邏輯:
首先去找字符串常量池找,看能不能找到「xyz」字符串的引用,若是字符串常量池中能找不到:
若是字符串常量池中能找到:
總結而言就是:
對於String s = new String("xyz");
這種形式建立字符串對象,若是字符串常量池中能找到,建立一個String對象;若是若是字符串常量池中找不到,建立兩個String對象。
對於String s = "xyz";
這種形式建立字符串對象,若是字符串常量池中能找到,不會建立String對象;若是若是字符串常量池中找不到,建立一個String對象。
因此,在平常開發中,能用String s = "xyz";
儘可能不用String s = new String("xyz");
,由於能夠少建立一個對象,節省一部分空間。
須要強調的是,字符串常量池存的不是字符串也不是String對象,而是一個個HashtableEntry,HashtableEntry裏面的value指向的纔是String對象,爲了避免讓表述變得複雜,我省略了HashtableEntry的存在,但不表明它就不存在。
上文提到的駐留就是新建HashtableEntry指向String對象,並把HashtableEntry存入字符串常量池的過程。
在網上一些文章中,一些做者多是爲了讓讀者更好的理解,省略了一些這些,必定要注意辨別區分。
達成以上共識以後,咱們再回顧一下那個老套的筆試題。
String s1 = new String("xyz"); String s2 = "xyz"; //爲何輸出的是false呢? System.out.println(s1 == s2); //爲何輸出的是true呢? System.out.println(s1.equals(s2));
有了上面的基礎以後,咱們畫出對應的內存圖,s1 == s2爲何是false就一目瞭然了。
由於equals方法比較的真正的char數據,而s1和s2最終指向的都是同一個char數組對象,因此s1.equals(s2)等於true。
關於他們最終指向的都是同一個char數組對象這一觀點,也能夠經過反射證實:
我修改了str1指向的String對象的value,str2指向的對象也被影響了。
如今,咱們再來看一下變式題:
String s1 = "aa"; String s2 = "bb"; String str1 = s1 + s2; String str2 = "aabb"; //爲何輸出的是false System.out.println(str1 == str2);
對於這個題目,咱們須要先看一下這段代碼的字節碼。
字節碼指令看不懂沒有關係,看我用紅色框框起來的部分就好了,能夠看到竟然出現了StringBuilder。
什麼意思呢,就是說String str1 = s1 + s2;
會被編譯器會優化成new StringBuilder().append("aa").append("bb").toString();
StringBuilder裏面的append方法就是對char數組進行操做,那StringBuilder的toString方法作了什麼呢?
從源碼中能夠看到,StringBuilder裏面的toString方法調用的是String類裏面的String(char value[], int offset, int count)
構造方法,這個方法作了什麼呢?
注意,並無駐留到字符串常量池裏面去,這個很關鍵!!!畫一個圖理解一下:
也就是說str2指向的String對象並無駐留到字符串常量池,而str1指向的對象駐留到字符串常量池裏面去了,且他們並非同一個對象。因此str1 == str2仍是false
由於複製一份char數組對象,因此若是咱們改變其中一個char數組的話,另外一個也不會形成影響:
把其中String變成醜比以後,另外一個仍是帥比,也說明了兩個String對象用的不是同一份char數組。
上面說到,調用StringBuilder的toString方法建立的String對象是不會駐留到字符串常量池的,那若是我偏要駐留到字符串常量池呢?有沒有辦法呢?
有的,String類的intern方法就能夠幫你完成這個事情。
以這段代碼爲例:
String s1 = "aa"; String s2 = "bb"; String str = s1 + s2; str.intern();
在執行str.intern();
以前,內存圖是這樣的:
在執行str.intern();
以後,內存圖是這樣的:
intern方法就是建立了一個HashtableEntry對象,並把value指向String對象,而後把HashtableEntry經過hash定位存到對應的字符串成常量池中。固然,前提是字符串常量池中原來沒有對應的HashtableEntry。
沒了,intern方法,就是這麼簡單,一句話給你說清楚了。
關於intern方法,還有一個頗有趣的故事,有興趣的能夠去看一下why神的這篇文章《深刻理解Java虛擬機》第2版挖的坑終於在第3版中被R大填平了
寫到這裏,好像只有一個坑沒有填。就是這個題爲何輸出的是true。
final String s3 = "cc"; final String s4 = "dd"; String str3 = s3 + s4; String str4 = "ccdd"; //爲何輸出的是true呢??? System.out.println(str3 == str4);
這道題和上面那道題相比,有點類似,在原來的基礎上加了兩個final關鍵字。咱們先看一下這段代碼的字節碼:
又是一段字節碼指令,不須要看懂,你點一下#4,竟然就能夠看到「ccdd」字符串。
原來,用final修飾後,JDK的編譯器會識別優化,會把String str3 = s3 + s4;
優化成String str3 = "ccdd"
。
因此原題就至關於:
String str3 = "ccdd"; String str4 = "ccdd"; //爲何輸出的是true呢??? System.out.println(str3 == str4);
這樣的題目還難嗎?是否是那無論str3和str4怎麼比,確定是相等的。
String對於Java程序員來講就是「最熟悉的陌生人」,你說String簡單,它確實簡單。你說它難,深究起來確實也有難度,但這些題目,只要你腦海裏有一副內存圖就會很簡單。
面試題也只會愈來愈難,這個行業看起來也愈來愈內卷,但只要我學的快,內卷就卷不到我。
好了,今天就寫到了,我要去打遊戲了。
但願這篇文章,能對你有一點幫助。
我對每一篇發出去的文章負責,文中涉及知識理論,我都會盡可能在官方文檔和權威書籍找到並加以驗證。但即便這樣,我也不能保證文章中每一個點都是正確的,若是你發現錯誤之處,歡迎指出,我會對其修正。
創做不易,爲了更好的表達,須要畫不少圖,這些都是我本身動手用PPT畫的,畫圖也很辛苦的!
因此,不要猶豫了,給點正反饋,答應我,一鍵三連(關注、點贊、再看)好嗎?
我是CoderW,一個程序員。
謝謝你的閱讀,咱們下期再見!
更多精彩關注微信公衆號【CoderW】