關于 Java String,這是面試的基礎,但是還有很多童鞋不能說清楚,所以本文將簡單而又透徹的說明一下那個讓你迷惑的 String
在 Java 中,我們有兩種方式創(chuàng)建一個字符串
String x = "abc"; String y = new String("abc");
你常見也常寫第一種,很少見第二種,但面試還總問這類問題,雙引號和構造器兩種形式創(chuàng)建字符串到底有什么差別呢?
先來看例子
例子 1
String a = "abcd"; String b = "abcd"; System.out.println(a == b); // True
System.out.println(a.equals(b)); // True
a == b 結(jié)果為 true,是因為 a 和 b 都指向 方法區(qū)(method area) 同一個字符串文字,內(nèi)存引用是同一個
當多次創(chuàng)建相同的字符串文字時,只存儲每個不同字符串值的一個副本。這個叫做字符串留駐/留用,Java 中所有編譯期字符串常量都會被自動留駐
例子 2
String c = new String("abcd"); String d = new String("abcd");
System.out.println(c == d); // False System.out.println(c.equals(d)); // True
c==d 結(jié)果為 false,因為 c 和 d 的引用指向堆中不同的對象,不同的對象肯定有不同的內(nèi)存引用
舉了兩個例子,文字描述有點懵?我們來試圖通過圖形來理解上述兩種情況:
也許你已經(jīng)看看出來了,一個是在方法區(qū),一個是在堆中,在 JVM 模型中這是兩個不同的區(qū)域,也許你面試時也經(jīng)常被問到吧,來看下圖:
再次提醒一下,所有 new 的對象都會在 Heap 中,這樣以后你就好區(qū)分了
運行期字符串留駐
上面說的字符串留駐是在編譯期,那么運行期可以嗎?答案是肯定的,我們需要一個函數(shù)來幫忙
String c = new String("abcd").intern(); String d = new
String("abcd").intern(); System.out.println(c == d); // Now true
System.out.println(c.equals(d)); // True
看到 c == d 結(jié)果為 true,你應該理解 intern (英文有拘留,軟禁的意思)的作用了,通過調(diào)用
intern()方法,就好比把創(chuàng)建的字符串拘留在方法區(qū)一樣了
在面試時甚至還會問你下面代碼創(chuàng)建了幾個對象:
String d = new String("abcd")
* 如果方法區(qū)已存在"abcd", 那么只創(chuàng)建一個 new String 的對象
* 如果方法區(qū)沒有"abcd", 那么要創(chuàng)建兩個對象,一個在方法區(qū),一個在堆中
所以,正常情況下我們沒必要使用構造器創(chuàng)建對象,因為這很可能會產(chǎn)生一個額外的沒用的對象,但是有例外哦,我們下面說
String s = "abcd"; s = s.concat("ef");
當我們想在字符串 s 后面拼接字符"ef"時,會在堆中創(chuàng)建一個新的對象,并將 s 的引用指向新創(chuàng)建的對象,由于 String 創(chuàng)建的是不可變對象,所以
String 類中的所有方法都不會改變它自身,而是返回一個新的字符串(快打開你的 IDE,看看是否每個操作String 的方法最后都是返回有 return
new String 字樣),到這里你也應該理解了一個道理:
如果我們需要一個字符串被修改,我們最好使用 StringBuffer 或者
StringBuilder,否則,由于每次操作字符串都會創(chuàng)建一個新的對象,而舊的對象不會有引用指向它,這樣我們會浪費很多垃圾回收的時間
到這里還沒完,你有沒有想過為什么 String 會被設置/制造成 final?
為什么 String 類被 final 修飾
談及這個問題我們需要一些倒推的或者相互約束思維來思考
字符串池的需求
字符串池(String intern
pool)是方法區(qū)域中的一個特殊存儲區(qū)域。當創(chuàng)建一個字符串時,如果該字符串已經(jīng)存在于池中,那么返回現(xiàn)有字符串的引用,而不是創(chuàng)建一個新對象。所以說,如果一個字符串是可變的,那么改變一個引用的值,將導致原本指向該值的引用獲取到錯誤的值
緩存 hashcode
字符串的hashcode在Java中經(jīng)常使用。例如,在HashMap或HashSet中。不可變保證hashcode始終是相同的,這樣就可以在不擔心更改的情況下兌現(xiàn)它。這意味著,不需要每次使用hashcode時都計算它。這樣更有效率。所以你會在
String 類中看到下面的成員變量的定義:
/** Cache the hash code for the string */ private int hash; // Default to 0
安全性
String被廣泛用作許多java類的參數(shù),例如網(wǎng)絡連接、打開文件等。如果字符串不是不可變的,連接或文件將被更改,這可能導致嚴重的安全威脅。該方法認為它連接到一臺機器上,但實際上并沒有。
可變字符串也可能導致反射中的安全問題,因為參數(shù)是字符串。
不可變對象天生是線程安全的
由于不可變對象不能被更改,所以它們可以在多個線程之間自由共享。這消除了同步的需求。
總之,出于效率和安全性的考慮,String 被設計為不可變的。這也是為什么在一般情況下,不可變類是首選的原因。
附加說明
關于不可變對象和不可變引用總是有同學搞不清楚
final User user = new User();
上面的代碼指的是 user 引用不能被更改指向內(nèi)存的其他地址,但是由于 User 是可變對象,我們可以調(diào)用 user 的 setter 方法修改其屬性
在String類中包含很多學問,包括你對JVM模型的理解,這也就是為什么面試官為什么喜歡問String,主要考察你的基本功
靈魂追問
* String 和基本類型的包裝類如 Integer 和 Long 都被 final 修飾,但為什么不建議作為 synchronized
同步塊的參數(shù)適用呢?
* 基本類型自動裝箱你知道發(fā)生了什么嗎?和上一個問題有關系
提高效率工具
Material Theme UI
這是一款 IDEA 的主題插件,安裝后,選擇 Material Palenight 主題,同時作出如下設置
設置完后,你的 IDEA 就是下面這樣,引起極度舒適
推薦閱讀
* 每天用SpringBoot,還不懂RESTful API返回統(tǒng)一數(shù)據(jù)格式是怎么實現(xiàn)的?
<https://mp.weixin.qq.com/s/q5cyK14WGiMm-R6NIFBcTg>
* 雙親委派模型:大廠高頻面試題,輕松搞定 <https://mp.weixin.qq.com/s/Dnr1jLebvBUHnziZzSfcrA>
* 面試還不知道BeanFactory和ApplicationContext的區(qū)別?
<https://mp.weixin.qq.com/s/YBQB086ADBjHUmwrFQrWew>
* 如何設計好的RESTful API <https://mp.weixin.qq.com/s/hR1TqkVzwZ_T8fuMnsM4hQ>
* 紅黑樹,超強動靜圖詳解,簡單易懂 <https://mp.weixin.qq.com/s/ilND8u_8HGSTSrJiMB4X8g>
歡迎持續(xù)關注公眾號:「日拱一兵」
* 前沿 Java 技術干貨分享
* 高效工具匯總 | 回復「工具」
* 面試問題分析與解答
* 技術資料領取 | 回復「資料」
以讀偵探小說思維輕松趣味學習 Java 技術棧相關知識,本著將復雜問題簡單化,抽象問題具體化和圖形化原則逐步分解技術問題,技術持續(xù)更新,請持續(xù)關注......
熱門工具 換一換