基礎面試,為什么面試官總喜歡問String?


關於 Java String,這是面試的基礎,但是還有很多童鞋不能說清楚,所以本文將簡單而又透徹的說明一下那個讓你迷惑的 String

在 Java 中,我們有兩種方式創建一個字符串

String x = "abc";
String y = new String("abc");

你常見也常寫第一種,很少見第二種,但面試還總問這類問題,雙引號和構造器兩種形式創建字符串到底有什么差別呢?

先來看例子

例子 1

String a = "abcd";
String b = "abcd";
System.out.println(a == b);  // True
System.out.println(a.equals(b)); // True

a == b 結果為 true,是因為 a 和 b 都指向 方法區(method area) 同一個字符串文字,內存引用是同一個

當多次創建相同的字符串文字時,只存儲每個不同字符串值的一個副本。這個叫做字符串留駐/留用,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 結果為 false,因為 c 和 d 的引用指向中不同的對象,不同的對象肯定有不同的內存引用

舉了兩個例子,文字描述有點懵?我們來試圖通過圖形來理解上述兩種情況:

也許你已經看看出來了,一個是在方法區,一個是在中,在 JVM 模型中這是兩個不同的區域,也許你面試時也經常被問到吧,來看下圖:

再次提醒一下,所有 new 的對象都會在 Heap 中,這樣以后你就好區分了

運行期字符串留駐

上面說的字符串留駐是在編譯期,那么運行期可以嗎?答案是肯定的,我們需要一個函數來幫忙

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 結果為 true,你應該理解 intern (英文有拘留,軟禁的意思)的作用了,通過調用 intern()方法,就好比把創建的字符串拘留在方法區一樣了

在面試時甚至還會問你下面代碼創建了幾個對象:

String d = new String("abcd")
  1. 如果方法區已存在"abcd", 那么只創建一個 new String 的對象
  2. 如果方法區沒有"abcd", 那么要創建兩個對象,一個在方法區,一個在堆中

所以,正常情況下我們沒必要使用構造器創建對象,因為這很可能會產生一個額外的沒用的對象,但是有例外哦,我們下面說

String s = "abcd";
s = s.concat("ef");

當我們想在字符串 s 后面拼接字符"ef"時,會在堆中創建一個新的對象,並將 s 的引用指向新創建的對象,由於 String 創建的是不可變對象,所以 String 類中的所有方法都不會改變它自身,而是返回一個新的字符串(快打開你的 IDE,看看是否每個操作String 的方法最后都是返回有 return new String 字樣),到這里你也應該理解了一個道理:

如果我們需要一個字符串被修改,我們最好使用 StringBuffer 或者 StringBuilder,否則,由於每次操作字符串都會創建一個新的對象,而舊的對象不會有引用指向它,這樣我們會浪費很多垃圾回收的時間

到這里還沒完,你有沒有想過為什么 String 會被設置/制造成 final?

為什么 String 類被 final 修飾

談及這個問題我們需要一些倒推的或者相互約束思維來思考

字符串池的需求

字符串池(String intern pool)是方法區域中的一個特殊存儲區域。當創建一個字符串時,如果該字符串已經存在於池中,那么返回現有字符串的引用,而不是創建一個新對象。所以說,如果一個字符串是可變的,那么改變一個引用的值,將導致原本指向該值的引用獲取到錯誤的值

緩存 hashcode

字符串的hashcode在Java中經常使用。例如,在HashMap或HashSet中。不可變保證hashcode始終是相同的,這樣就可以在不擔心更改的情況下兌現它。這意味着,不需要每次使用hashcode時都計算它。這樣更有效率。所以你會在 String 類中看到下面的成員變量的定義:

/** Cache the hash code for the string */
private int hash; // Default to 0

安全性

String被廣泛用作許多java類的參數,例如網絡連接、打開文件等。如果字符串不是不可變的,連接或文件將被更改,這可能導致嚴重的安全威脅。該方法認為它連接到一台機器上,但實際上並沒有。可變字符串也可能導致反射中的安全問題,因為參數是字符串

不可變對象天生是線程安全的

由於不可變對象不能被更改,所以它們可以在多個線程之間自由共享。這消除了同步的需求。

總之,出於效率和安全性的考慮,String 被設計為不可變的。這也是為什么在一般情況下,不可變類是首選的原因。

附加說明

關於不可變對象和不可變引用總是有同學搞不清楚

final User user = new User();

上面的代碼指的是 user 引用不能被更改指向內存的其他地址,但是由於 User 是可變對象,我們可以調用 user 的 setter 方法修改其屬性

在String類中包含很多學問,包括你對JVM模型的理解,這也就是為什么面試官為什么喜歡問String,主要考察你的基本功

靈魂追問

  1. String 和基本類型的包裝類如 Integer 和 Long 都被 final 修飾,但為什么不建議作為 synchronized 同步塊的參數適用呢?
  2. 基本類型自動裝箱你知道發生了什么嗎?和上一個問題有關系

提高效率工具

Material Theme UI

這是一款 IDEA 的主題插件,安裝后,選擇 Material Palenight 主題,同時作出如下設置

設置完后,你的 IDEA 就是下面這樣,引起極度舒適


推薦閱讀


歡迎持續關注公眾號:「日拱一兵」

  • 前沿 Java 技術干貨分享
  • 高效工具匯總 | 回復「工具」
  • 面試問題分析與解答
  • 技術資料領取 | 回復「資料」

以讀偵探小說思維輕松趣味學習 Java 技術棧相關知識,本着將復雜問題簡單化,抽象問題具體化和圖形化原則逐步分解技術問題,技術持續更新,請持續關注......


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM