String str = new String("abc")到底創建幾個String對象

String str=new String("abc");

        緊接着這段代碼之後的往往是這個問題,那就是這行代碼究竟創建了幾個String對象呢?相信大家對這道題並不 陌生,答案也是衆所周知的,2個。接下來我們就從這道題展開,一起回顧一下與創建String對象相關的一些 JAVA知識。

      我們可以把上面這行代碼分成   String str   、=  、 "abc"   和   new String()  四部分來看待。

     String str只是定義了一個名 爲str的String類型的變量,因此它並沒有創建對象;

      = 是對變量str進行初始化,將某個對象的引用(或者叫句 柄)賦值給它,顯然也沒有創建對象;

      現在只剩下new String("abc")了。那麼,new String("abc")爲什麼又能 被看成"abc"和new String()呢?

      我們來看一下被我們調用了的String的構造器:
   

  public String(String original) { 
      //other code ...
 }


大家都知道,我們常用的創建一個類的實例(對象)的方法有以下兩種:

1. 使用new創建對象。

2. 調用Class類的newInstance方法,利用反射機制創建對象。


我們正是使用new調用了String類的上面那個構造器方法創建了一個對象,並將它的引用賦值給了str變量。同 時我們注意到,被調用的構造器方法接受的參數也是一個String對象,這個對象正是"abc"。由此我們又要引入 另外一種創建String對象的方式的討論——引號內包含文本。(上面 String str=new String("abc"); 創建的是兩個String對象
這種方式是String特有的,並且它與new的方式存在很大區別。

String str="abc";


毫無疑問,這行代碼創建了一個String對象。

String a="abc"; String b="abc";//創建了一個String對象

 

String a="ab"+"cd";//創建了一個String對象


     有點奇怪嗎?說到這裏,我們就需要引入對字符串池相關知識的回顧了。


       在JAVA虛擬機(JVM)中存在着一個字符串池,其中保存着很多String對象,並且可以被共享使用,因此它提 高了效率。由於String類是final的,它的值一經創建就不可改變,因此我們不用擔心String對象共享而帶來程序 的混亂。字符串池由String類維護,我們可以調用intern()方法來訪問字符串池。

       我們再回頭看看  String a="abc";,這行代碼被執行的時候,JAVA虛擬機首先在字符串池中查找是否已經存在了 值爲"abc"的這麼一個對象,它的判斷依據是String類equals(Object obj)方法的返回值。如果有,則不再創建 新的對象,直接返回已存在對象的引用;如果沒有,則先創建這個對象,然後把它加入到字符串池中,再將它 的引用返回。因此,我們不難理解前面三個例子中頭兩個例子爲什麼是這個答案了。
      對於第三個例子:

String a="ab"+"cd";


        由於常量的值在編譯的時候就被確定了。在這裏,"ab"和"cd"都是常量,因此變量a的值在編譯時就可以確定。 這行代碼編譯後的效果等同於:

String a="abcd";


        因此這裏只創建了一個對象"abcd",並且它被保存在字符串池裏了。
 現在問題又來了,是不是所有經過“+”連接後得到的字符串都會被添加到字符串池中呢?我們都知道“==” 可以用來比較兩個變量,它有以下兩種情況:

      1. 如果比較的是兩個基本類型(char,byte,short,int,long,float,double,boolean),則是判 斷它們的值是否相等。    

     2. 如果表較的是兩個對象變量,則是判斷它們的引用是否指向同一個對象。


下面我們就用“==”來做幾個測試。

  爲了便於說明,我們把指向字符串池中已經存在的對象也視爲該對象被加 入了字符串池:

public class StringTest { 
    public static void main(String[] args) { 
        String a = "ab";// 創建了一個對象,並加入字符串池中 
        System.out.println("String a = \"ab\";"); 
        String b = "cd";// 創建了一個對象,並加入字符串池中 
        System.out.println("String b = \"cd\";"); 
        String c = "abcd";// 創建了一個對象,並加入字符串池中
        String d = "ab" + "cd"; // 如果d和c指向了同一個對象,則說明d也被加入了字符串池 
        if (d == c) { 
            System.out.println("\"ab\"+\"cd\" 創建的對象 \"加入了\" 字符串池中"); 
        }else { 
            System.out.println("\"ab\"+\"cd\" 創建的對象 \"沒加入\" 字符串池中");
        }
        String e = a + "cd"; // 如果e和c指向了同一個對象,則說明e也被加入了字符串池 
        if (e == c) { 
            System.out.println(" a  +\"cd\" 創建的對象 \"加入了\" 字符串池中"); 
        } else { 
            System.out.println(" a  +\"cd\" 創建的對象 \"沒加入\" 字符串池中"); }
            String f = "ab" + b; // 如果f和c指向了同一個對象,則說明f也被加入了字符串池 
        if (f == c) {
            System.out.println("\"ab\"+ b   創建的對象 \"加入了\" 字符串池中");
        }  else { 
            System.out.println("\"ab\"+ b   創建的對象 \"沒加入\" 字符串池中");
        }
        String g = a + b; // 如果g和c指向了同一個對象,則說明g也被加入了字符串池 
        if (g == c) { 
            System.out.println(" a  + b   創建的對象 \"加入了\" 字符串池中"); 
        }  else { 
            System.out.println(" a  + b   創建的對象 \"沒加入\" 字符串池中"); }
}
}


運行結果如下:

1. String a = "ab"; 
2. String b = "cd"; 
3. "ab"+"cd" 創建的對象 "加入了" 字符串池中 
4. a +"cd" 創建的對象 "沒加入" 字符串池中 
5. "ab"+ b 創建的對象 "沒加入" 字符串池中 
6. a + b 創建的對象 "沒加入" 字符串池中


        從上面的結果中我們不難看出,只有使用引號包含文本的方式創建的String對象之間使用“+”連接產生的新對 象纔會被加入字符串池中。對於所有包含new方式新建對象(包括null)的“+”連接表達式,它所產生的新對 象都不會被加入字符串池中,對此我們不再贅述。
但是有一種情況需要引起我們的注意。請看下面的代碼:

public class StringStaticTest { // 常量A

    public static final String A = "ab";
    // 常量B 
    public static final String B = "cd";
    public static void main(String[] args) { 
        // 將兩個常量用+連接對s進行初始化 
        String s = A + B; String t = "abcd"; 
        if (s == t) { 
            System.out.println("s等於t,它們是同一個對象"); 
        } else { 
            System.out.println("s不等於t,它們不是同一個對象"); 
        } 
    }
}


這段代碼的運行結果如下:

•s等於t,它們是同一個對象


這又是爲什麼呢?原因是這樣的,對於常量來講,它的值是固定的,因此在編譯期就能被確定了,而變量的值 只有到運行時才能被確定,因爲這個變量可以被不同的方法調用,從而可能引起值的改變。在上面的例子中,A 和B都是常量,值是固定的,因此s的值也是固定的,它在類被編譯時就已經確定了。也就是說:

String s = A+B;


等同於:

String s="ab"+"cd";


我對上面的例子稍加改變看看會出現什麼情況:

public class StringStaticTest { 
    // 常量A 
    public static final String A;

    // 常量B 
    public static final String B;
    static {
        A = "ab"; B = "cd";
    }
    public static void main(String[] args) {
     // 將兩個常量用+連接對s進行初始化 
    String s = A + B; String t = "abcd"; 
    if (s == t) {
        System.out.println("s等於t,它們是同一個對象"); 
    } else { 
        System.out.println("s不等於t,它們不是同一個對象"); 
    } 
    }
}


它的運行結果是這樣:

• s不等於t,它們不是同一個對象


       只是做了一點改動,結果就和剛剛的例子恰好相反。我們再來分析一下。A和B雖然被定義爲常量(只能被賦值 一次),但是它們都沒有馬上被賦值。在運算出s的值之前,他們何時被賦值,以及被賦予什麼樣的值,都是個 變數。因此A和B在被賦值之前,性質類似於一個變量。那麼s就不能在編譯期被確定,而只能在運行時被創建 了。


        由於字符串池中對象的共享能夠帶來效率的提高,因此我們提倡大家用引號包含文本的方式來創建String對象, 實際上這也是我們在編程中常採用的。
接下來我們再來看看intern()方法,它的定義如下:

public native String intern();



       這是一個本地方法。在調用這個方法時,JAVA虛擬機首先檢查字符串池中是否已經存在與該對象值相等對象存 在,如果有則返回字符串池中對象的引用;如果沒有,則先在字符串池中創建一個相同值的String對象,然後再 將它的引用返回。


我們來看這段代碼:

public class StringInternTest { 
    public static void main(String[] args) { 
        // 使用char數組來初始化a,避免在a被創建之前字符串池中已經存在了值爲"abcd"的對象 
        String a = new String(new char[] { 'a', 'b', 'c', 'd' }); 
        String b = a.intern(); 
        if (b == a) { 
            System.out.println("b被加入了字符串池中,沒有新建對象");
        } else { 
            System.out.println("b沒被加入字符串池中,新建了對象"); 
        } 
    } 
}


運行結果:

 1. b沒被加入字符串池中,新建了對象


        如果String類的intern()方法在沒有找到相同值的對象時,是把當前對象加入字符串池中,然後返回它的引用的 話,那麼b和a指向的就是同一個對象;否則b指向的對象就是JAVA虛擬機在字符串池中新建的,只是它的值與a 相同罷了。上面這段代碼的運行結果恰恰印證了這一點。


   最後我們再來說說String對象在JAVA虛擬機(JVM)中的存儲,以及字符串池與堆(heap)和棧(stack)的 關係。

   我們首先回顧一下堆和棧的區別:

       • 棧(stack):主要保存基本類型(或者叫內置類型)(char、byte、short、int、long、float、 double、boolean)和對象的引用,數據可以共享,速度僅次於寄存器(register),快於堆。

       • 堆(heap):用於存儲對象。
我們查看String類的源碼就會發現,它有一個value屬性,保存着String對象的值,類型是char[],這也正說明 了字符串就是字符的序列。
           當執行   String a="abc";   時,JAVA虛擬機會在棧中創建三個char型的值 'a' 、 'b'  和  'c',然後在堆中創建一個String對象,它的值(value)是剛纔在棧中創建的三個char型值組成的數組{'a','b','c'},最後這個新創建的String對象 會被添加到字符串池中。如果我們接着執行  String b  =  new String("abc");代碼, 由於"abc"已經被創建並保存於 字符串池中,因此JAVA虛擬機只會在堆中新創建一個String對象,但是它的值(value)是共享前一行代碼執行 時在棧中創建的三個char型值值'a'、'b'和'c'。
說到這裏,我們對於篇首提出的  String str  =  new  String("abc") 爲什麼是創建了兩個對象這個問題就已經相當明 了了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章