new String("aaa")建立了幾個String物件?
- String str=new String("aaa");
這行程式碼究竟建立了幾個String物件呢?答案是2個,而不是3個。由於new String("aaa")相當於"aaa"與一個就是創建出來的放在堆時原例項物件,而另一個就是放在常量池中的 "aaa" 物件,當然這裡的str本身只是一個引用,放在棧裡,用來指向堆中創建出來的物件。
常量池(constant pool)指的是在編譯期被確定,並被儲存在已編譯的.class檔案中的一些資料。它包括了關於類、方法、介面等中的常量,也包括字串常量。
- String str="aaa";
只建立1個物件。這裡涉及到字串常量池,在JVM中有一個字串池,它用來儲存很多可以被共享的String物件,這樣如果我們在使用同樣字面字串時,它就使用字串池中同字面的字串。當然我們可以使用String物件的intern()方法來訪問String物件在字串池中所對應的常量物件。
上面這行程式碼被執行的時候,JVM先到字串池中查詢,看是否已經存在值為"aaa"的物件,如果存在,則不再建立新的物件,直接返回已存在物件的引用;如果不存在,則先建立這個物件,然後把它加入到字串池中,再將它的引用返回。
- String str1="aaa";
- String str2="aaa";
也只建立1個物件。能過上面的解釋這個就更清楚了,在執行第二行程式碼時,aaa字串物件在池中已存在,所以直接返回池中已存在的那個字串物件。
- String str="aaa"+"bbb";
還是隻建立1個物件。由於常量字串是在編譯的時候就也被確定的,又因"aaa"和"bbb"都是常量,因此變數str的值在編譯時就可以確定。這行程式碼編譯後的與String str="aaabbb";是一樣的,這與我們平時好像不太一樣啊?一般使用“+”連線兩個字串都會產生另一個新的字元物件。下面我們看一下例子就明白了:
- String str1 = "aaa";
- String str2 = "bbb";
- String str3 = "aaabbb";
- String str4 = "aaa" + "bbb";//不會產生新的字串物件
- System.out.println(str3 == str4);//true
- str4 = str1 + "bbb";//會產生新的字串物件
- System.out.println(str3 == str4);//false
- str4 = str1 + str2;//會產生新的字串物件
-
System.out.println(str3 == str4);//false
從上面例子我們就可以得出:使用“+”連線的兩個字串本身就是字面常量字串時,如果池中存在這樣連線後的字串,則是不會重新建立物件,而是直接引用池中的字串物件;如果“+”連線的兩字串中只要有一個不是字面常量串(即定義過的),是會產生新的字串物件。
凡事也有例外,這個也不例外:如果“+”連線的字串中兩個或一個不是“字面常量”,但如果定義成常量字串時,情況又有變化:
- final String str1 = "aaa";
- final String str2 = "bbb";
- String str3 = "aaabbb";
- String str4 = str1 + str2;
- System.out.println(str3 == str4);//true
但如果先定義final字串,但未在定義處初始化,而初始化在塊中,如下:
- //此時str1與str2相當於變數,而不是常,因為塊是在執行時才能確定,在編譯時不能確定
- final static String str1;
- final static String str2;
- static {
- str1 ="aaa";
- str2 ="bbb";
- }
- public static void main(String[] args){
- String str3 = str1 + str2;
- String str4 ="aaabbb";
- System.out.println(str3==str4); //輸出為false
- }
- String str=" ";與String str=new String();
- str=" "會放入池中,但new String()不會放入池中。
String的intern()方法
“當呼叫 intern 方法時,如果池已經包含一個等於此 String 物件的字串(該物件由 equals(Object) 方法確定),則返回池中的字串;否則,將此 String 物件新增到池中,並且返回此 String 物件的引用。它遵循對於任何兩個字串 s 和 t,當且僅當 s.equals(t) 為 true 時,s.intern() == t.intern() 才為 true”,這是jdk文件原文註解。
現在我們回到最開頭的那個例子,為什麼String str=new String("aaa");會產生2個物件?一個是"aaa"又作為字串建構函式的引數,但"aaa"自己就是一個字串,在傳進建構函式前就已建立了一個字元物件,實質上與就好比是第二個例項:String str="aaa"; ,它建立的字串物件會放入到池中,並且引用的也是池中的那個字串物件;另一個就是通過new String()建構函式建立的。所以new String("aaa")會產生兩個物件,也就是說通過此種方式建立字串物件時,會先將字串引數物件放入物件池,然後另外建立一個字串物件。
理解完new String("aaa")為什麼會產生兩個物件,我們再來看看new String(char value[])這樣的方式建立時,又會不會把字串物件放入到池中呢?答案是不會的。因為傳遞給建構函式的是一個字元陣列,而不是像前面傳遞的是一個字面常量字串引數那樣,將字串引數本身放入池中。那麼我們現在如果證明new String(char value[])未將字串物件放入池中,我們可以寫一個簡單的測試,執行時開啟XP的工作管理員,檢視作業系統的記憶體使用情況就可以初步確認:
- int size = 10000000;
- char c[] = new char[size];
- for (int i = 0; i < size; i++) {
- c[i] = 'a';
- }
- //使用帶字元陣列引數建構函式建立字串時,字串物件不會放入字串池
- String str1 = new String(c);
- System.out.println("String字串物件建立完畢...");
- Thread.sleep(5000);
- str1.intern();//到裡會看見記憶體增加
- System.out.println("第一次呼叫intern()完畢...");
- Thread.sleep(5000);
- str1.intern();//再過5秒將看不到記憶體增長,因為池中有了,不會再放入,所以記憶體無變化
- System.out.println("第二次呼叫intern()完畢...");
- Thread.sleep(5000);
所以建立字串物件放入並放入池中有二種方式:第一種就是直接使用字面常量定義字串時,如 String str="aaa"; ,str會引用放入池中的物件;第二種就是使用帶字串引數的字串建構函式,並且此時傳入的引數值要是字串常量形式,而不能是變數的形式,也就是說只能是 String str=new String("aaa");形式,而不能是先定義 String s = "aaa",然後再使用 String str=new String(s);來建立物件,new
String(s);此時只建立一個物件,但如果池中不存在時我們可以使用intern方法將它放入池中。當然上面放入池中的前提是池中還不存在這些字串物件。
其實,當我們仔細研究時,發現放入池中只實質上只存在一種時機,那就是:直接使用字面常量字串時。上面所說的兩種時機實質上就是直接使用了字面常的字串而將其放入池中的。
上面多處提到了JVM中的堆疊,下面小結一下各自原作用:
棧用來儲存基本型別與物件的引用的,基本型在建立前會檢視Stack中是否已經有, 有則指向, 沒有則建立。
String內部是以字串陣列來儲存字串的,因此可以認為與char[]等同, String a= "abc",首先在Heap中創一個物件,再到Stack中找char[]是否存在,有則指向該地址, 無則在Stack中建立陣列。
new出來的都是在Heap中,堆是用於儲存物件的。