1. 程式人生 > >一段簡單的關於字串的 Java 程式碼竟考察了這麼多東西

一段簡單的關於字串的 Java 程式碼竟考察了這麼多東西

下面的程式碼執行結果是什麼?解釋一下為什麼會有這些差異。

String s1 = "hello";
String s2 = s1 + ",world";
String s3 = "hello" + ",world";
String s4 = "hello,world";
String s5 = new String("hello,world");

System.out.println(s2.equals(s4)); // true
System.out.println(s2==s4);     // false
System.out.println(s3==s4);     // true
System.out.println(s4==s5);     // false

看似簡單的程式碼,卻有很多學問在裡面。在此之前先非常簡單地說一下JVM中的棧和堆,棧一般存放的是區域性變數,物件的引用等;堆一般放物件、常量等,在 jdk1.7 以後的版本,字串常量池也在堆中。

字串常量池

  一般情況下,建立字串物件有兩種方式,一種是字面值建立,一種是 new 建立,這兩者是不一樣的。

  如果是字面值建立的方式,如 String s4="hello,world",JVM會先去字串常量池中尋找有沒有“hello,world”這個字串,若有,則將其地址給 s4;若沒有,則先在常量池裡建立“hello,world”,然後再把地址給s4;而通過 new 的方式建立物件,則是在堆中建立“hello,world”物件,s5 指向這個物件。還是看圖吧,比較直觀:

  

equals() 和 == 

  這是 Java 面試常考點,有人可能會說 equals() 比較的是值,而 == 比較的是物件的引用(沒錯是我),直到被面試官吊打,才會老老實實去看 Object 類中 equals() 的原始碼:

    

  可以看到,equals() 方法體中,返回的是 兩個物件的引用的比較結果 。但是,兩個 String 型別 == 和 equals() 有時候結果卻不一樣。那是因為在 String 類中,equals() 被重寫了:

  

  可以看到,它先對比兩個引用;若一樣則直接返回 true,不一樣就對它們的值進行比較。因此,可以對 == 和 equals() 更好的區別了:

  

字串的拼接

  String 類被 final 修飾,因此字串不能修改,當兩個字串相加時,是先生成 StringBuilder 物件,然後通過 append() 的方式將兩個字串拼接,再呼叫 toString() 方法生成新的字串物件。所以只考慮 s1 和 s2,他們在 JVM 中是這樣的:

  

  但是像 String s3 = "hello" + ",world" 這樣直接兩個字面值相加的,java檔案在編譯期間就已經將這條語句做了優化,將其直接變成 "hello,world",等到執行的時候就查詢字串常量池,因此 s3 == s4 返回的結果就為 true。

String、StringBuilder、StringBuffer

  既然上面說到了 StringBuilder,那就順便簡單說一下這三者之間的區別。

  String 是常量,且每次需要在原來字串基礎上擴充套件都需要新建物件,導致速度會稍微慢一點,而且佔記憶體,因此對於需要經常擴充套件的字串,可以使用 StringBuilder 和 StringBuffer。但是 StringBuilder 和 StringBuffer 又有區別,即時兩者很像,在速度上 StringBuilder 會快一些,因為 StringBuffer 的操作加了 synchronized ,即加了鎖,使得操作相對比較慢,就舉 append() 為例子,StringBuffer 中此方法的原始碼如下:

  

   所以,一般在單執行緒的情況下,可以選擇使用 StringBuilder;在多執行緒的情況下可以選擇 StringBuffer .

總結

  回到一開始的程式,我們可以大致地畫出在 JVM 中的儲存: