- 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";
- /*
- * 因爲str1與str2都定義成了常量,所以編譯時就能確定,編譯時就會將常量替換,等同於
- * str4 = "aaa"+"bbb",因此不產生新對象
- */
- 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中,堆是用於存儲對象的。