public class Test { app
public static final String MESSAGE="taobao"; 優化
public static void main(String[] args) { spa
String a = "tao"+"bao"; 設計
String b = "tao"; 對象
String c = "bao"; 內存
System.out.println(a==MESSAGE); System.out.println( (b+c)==MESSAGE); 字符串
} 編譯器
} string
對於這道題,考察的是對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