由阿里一道筆試題引發的思考

 阿里的一道筆試題

public class Test {
    public static final String MESSAGE="taobao";
    public static void main(String[] args) {
       String a = "tao"+"bao";
       String b = "tao";
       String c = "bao";
       System.out.println(a==MESSAGE);    
       System.out.println( (b+c)==MESSAGE);  
    }
}


 

對於這道題,考察的是對String類型的認識以及編譯器優化。Java中String不是基本類型,但是有些時候和基本類型差不多,如String b = "tao"; 可以對變量直接賦值,而不用 new 一個對象(當然也可以用 new)。所以String這個類型值得好好研究下。

Java中的變量和基本類型的值存放於棧內存,而new出來的對象本身存放於堆內存,指向對象的引用還是存放在棧內存。例如如下的代碼:

    int i=1;
    String s = new String("Hello World"); 

變量i和s以及1存放在棧內存,而s指向的對象”Hello World”存放於堆內存。

棧內存的一個特點是數據共享,這樣設計是爲了減小內存消耗,前面定義了i=1,i和1都在棧內存內,如果再定義一個j=1,此時將j放入棧內存,然後查找棧內存中是否有1,如果有則j指向1。如果再給j賦值2,則在棧內存中查找是否有2,如果沒有就在棧內存中放一個2,然後j指向2。也就是如果常量在棧內存中,就將變量指向該常量,如果沒有就在該棧內存增加一個該常量,並將變量指向該常量。

如果j++,這時指向的變量並不會改變,而是在棧內尋找新的常量(比原來的常量大1),如果棧內存有則指向它,如果沒有就在棧內存中加入此常量並將j指向它。這種基本類型之間比較大小和我們邏輯上判斷大小是一致的。如定義i和j是都賦值1,則i==j結果爲true。==用於判斷兩個變量指向的地址是否一樣。i==j就是判斷i指向的1和j指向的1是同一個嗎?當然是了。對於直接賦值的字符串常量(如String s=“Hello World”;中的Hello World)也是存放在棧內存中,而new出來的字符串對象(即String對象)是存放在堆內存中。如果定義String s=“Hello World”和String w=“Hello World”,s==w嗎?肯定是true,因爲他們指向的是同一個Hello World。

堆內存沒有數據共享的特點,前面定義的String s = new String("Hello World");後,變量s在棧內存內,Hello World 這個String對象在堆內存內。如果定義String w =new String("Hello World");,則會在堆內存創建一個新的String對象,變量w存放在棧內存,w指向這個新的String對象。堆內存中不同對象(指同一類型的不同對象)的比較如果用==則結果肯定都是false,比如s==w?當然不等,s和w指向堆內存中不同的String對象。如果判斷兩個String對象相等呢?用equals方法。

說了這麼多隻是說了這道題的鋪墊知識,還沒進入主題,下面分析這道題。MESSAGE成員變量及其指向的字符串常量肯定都是在棧內存裏的,變量a運算完也是指向一個字符串“taobao”啊?是不是同一個呢?這涉及到編譯器優化問題。對於字符串常量的相加,在編譯時直接將字符串合併,而不是等到運行時再合併。也就是說

String a = "tao"+"bao";和String a = "taobao";編譯出的字節碼是一樣的。所以等到運行時,根據上面說的棧內存是數據共享原則,a和MESSAGE指向的是同一個字符串。而對於後面的(b+c)又是什麼情況呢?b+c只能等到運行時才能判定是什麼字符串,編譯器不會優化,想想這也是有道理的,編譯器怕你對b的值改變,所以編譯器不會優化。運行時b+c計算出來的"taobao"和棧內存裏已經有的"taobao"是一個嗎?不是。b+c計算出來的"taobao"應該是放在堆內存中的String對象。這可以通過System.out.println( (b+c)==MESSAGE);的結果爲false來證明這一點。如果計算出來的b+c也是在棧內存,那結果應該是true。Java對String的相加是通過StringBuffer實現的,先構造一個StringBuffer裏面存放”tao”,然後調用append()方法追加”bao”,然後將值爲”taobao”的StringBuffer轉化成String對象。StringBuffer對象在堆內存中,那轉換成的String對象理所應當的也是在堆內存中。下面改造一下這個語句System.out.println( (b+c).intern()==MESSAGE);結果是true,intern()方法會先檢查String池(或者說成棧內存)中是否存在相同的字符串常量,如果有就返回。所以intern()返回的就是MESSAGE指向的"taobao"。再把變量b和c的定義改一下

       final String b = "tao";
       final String c = "bao";
       System.out.println( (b+c)==MESSAGE);



現在b和c不可能再次賦值了,所以編譯器將b+c編譯成了”taobao”。因此,這時的結果是true。

在字符串相加中,只要有一個是非final類型的變量,編譯器就不會優化,因爲這樣的變量可能發生改變,所以編譯器不可能將這樣的變量替換成常量。例如將變量b的final去掉,結果又變成了false。這也就意味着會用到StringBuffer對象,計算的結果在堆內存中。

    如果對指向堆內存中的對象的String變量調用intern()會怎麼樣呢?實際上這個問題已經說過了,(b+c).intern(),b+c的結果就是在堆內存中。對於指向棧內存中字符串常量的變量調用intern()返回的還是它自己,沒有多大意義。它會根據堆內存中對象的值,去查找String池中是否有相同的字符串,如果有就將變量指向這個string池中的變量。

      String a = "tao"+"bao";
      String b = new String("taobao");
      System.out.println(a==MESSAGE); //true 
      System.out.println(b==MESSAGE);  //false
      b = b.intern();
      System.out.println(b==MESSAGE); //true
      System.out.println(a==a.intern()); //true


 

參考博文:

http://topic.csdn.net/u/20090512/22/934b9543-f793-4517-bb70-3a412c8a5557.html

http://olduan.blog.163.com/blog/static/933079052009525258690/

http://blog.csdn.net/lichd_lcs/archive/2005/04/13/345947.aspx


 

 

 

 


 

發佈了48 篇原創文章 · 獲贊 18 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章