建立過程分析:當執行String s = new String(「abc」);時,JVM首先在String Pool中查看是否存在字符串對象「abc」,若是不存在該對象,則先在String Pool中建立一個新的字符串對象「abc」,而後執行new String(「abc」)構造方法,在Heap裏又建立一個新的字符串對象「abc」(new出來的對象都放在Heap裏面),並將引用s指向Heap中建立的新對象;若是已存在該對象,則不用建立新的字符串對象「abc」,而直接使用String Pool中已存在的對象「abc」, 而後執行new String(「abc」)構造方法,在Heap裏又建立一個新的字符串對象「abc」,並將引用s指向Heap中建立的新對象。
注意:使用new String(「」)建立的字符串對象時,會在運行期建立新對象存儲到Heap中。所以,new String(「abc」)建立字符串對象時,會建立2個對象,編譯期在String Pool中建立一個,運行時Heap中建立一個。
這裏使用了
- * Initializes a newly created String object so that it
* represents the same sequence of characters as the argument; in other
* words, the newly created string is a copy of the argument string. Unless
* an explicit copy of original is needed, use of this
* constructor is unnecessary since Strings are immutable.
- public String(String original)
翻譯以下:這個構造方法來用來初始化新建立的String對象,使之具備與參數有相同的字符串順序流。換句話說,新建立的字符串對象只是一個參數的拷貝。由於String對象是不可改變的,因此除非有必要顯式地生成參數的副本,不然不必用此構造方法來構造String對象。
這個構造方法,做用:初始化一個新建立的String對象,使其表示一個與參數相同的字符序列;換句話說,新建立的字符串是該參數字符串的副本。
例如:
1 String s1 = new String("abc");
2 String s2 = new String("abc");
3 System.out.println(s1 == s2);//false
結果說明:字符串常量生成的字符串對象在String Pool中只有一個拷貝,且它是在編譯期就被肯定了,因此「s1 == s2」;「abc」和「def」都是字符串常量,當一個字符串由多個字符串常量鏈接而成時,它本身也確定是字符串常量(它在編譯期就被解析爲一個字符串對象了,即class文件中就已經存在「abcdef」),因此在字符串生成字符串對象時,s3也是String Pool中「abcdef」的一個引用。故JVM對於字符串常量的"+"號鏈接,在程序編譯期,JVM就將常量字符串的"+"鏈接優化爲鏈接後的值。
例程2:
1 String s1 = "abc";
2 String s2 = "def";
3 String s3 = "abcdef";
4 String s4 = "abc"+"def";
5 String s5 = s1 + "def";
6 String s6 = "abc"+s2;
7 String s7 = s1 + s2;
8 System.out.println(s3 == s4);
9 System.out.println(s3 == s5);
10 System.out.println(s3 == s6);
11 System.out.println(s3 == s7);
12
13 運行結果以下:
14 true
15 false
16 false
17 false
結果說明:JVM對於有字符串引用存在的字符串"+"鏈接中,而引用的值在程序編譯期是沒法肯定的,即s1 + 「def」沒法被編譯器優化,只有在程序運行期來動態分配並將鏈接後的新地址賦給s5。
例程3:
1 final String s1 = "abc";
2 String s2 = "def";
3 String s3 = "abcdef";
4 String s4 = "abc"+"def";
5 String s5 = s1 + "def";
6 String s6 = "abc"+s2;
7 String s7 = s1 + s2;
8 System.out.println(s3 == s4);
9 System.out.println(s3 == s5);
10 System.out.println(s3 == s6);
11 System.out.println(s3 == s7);
12
13 運行結果以下:
14 true
15 true
16 false
17 false
例程4:
1 final String s1 = "abc";
2 final String s2 = "def";
3 String s3 = "abcdef";
4 String s4 = "abc"+"def";
5 String s5 = s1 + "def";
6 String s6 = "abc"+s2;
7 String s7 = s1 + s2;
8 System.out.println(s3 == s4);
9 System.out.println(s3 == s5);
10 System.out.println(s3 == s6);
11 System.out.println(s3 == s7);
12
13 運行結果以下:
14 true
15 true
16 true
17 true
結果說明:例程3和例程4與例程2的區別是,例程3在字符串s1前加了final修飾,例程4在字符串s1和s2前都加了final修飾。對於final修飾的變量,它在編譯時被解析爲常量值的一個本地拷貝存儲到本身的常量池中或嵌入到它的字節碼流中。因此此時的s1 + 「def」和"abc" + "def"效果是同樣的。接着後面兩個含引用的字符串鏈接,JVM會進行相同的處理。故上面程序後面三個的結果爲true。
例程5:
1 public static void main(String args[]){
2 String s1 = "abc";
3 final String s2 = getDef();
4 String s3 = "abcdef";
5 String s4 = "abc"+s2;
6 String s5 = s1 + s2;
7 System.out.println(s3 == s4);
8 System.out.println(s3 == s5);
9 }
10 private static String getDef(){
11 return "def";
12 }
13
14 程序運行結果以下:
15 false
16 false
結果說明:JVM對於方法調用給字符串引用賦值的狀況,引用指向字符串的值在編譯期是沒法肯定的,只有在程序運行調用方法後,將方法的返回值「def」和「abc」動態鏈接並分配新地址賦值給s4,因此上述程序的結果都爲false。
經過以上的例子可知:
String s = "a" + "b" + "c";
等價於:
String s = "abc";
編譯期,直接優化,進行常量鏈接。
對於:
String a = "a";
String b = "b";
String c = "c";
String s = a + b + c;
就不等價於:
String s = "abc";
最終結果等於:
StringBuilder builder = new StringBuilder ();
builder.append(a);
builder.append(b);
builder.append(c);
String s = builder.toString();
去看StringBuilder的toString()方法:
- public String toString() {
- // Create a copy, don't share the array
- return new String(value, 0, count);
- }
能夠發現是經過new String(..)返回了一個String對象,也就是說在堆中建立了對象。這時候會不會在池中出現"abc"這個對象呢?(question還沒解決)
生成String s的過程當中,編譯器使用sb執行的過程:建立一個StringBuffer對象,使用append()向此StringBuffer對象直接添加新的字符串(而不是每次製做一個新的副本)。
對於String c = "c";String s = "a" + "b" + c;,編譯器將會先將"a" + "b"做爲編譯時常量,優化生成成字面常量"ab" ,而後生成一個StringBuilder對象,接着調用兩次 append()方法,即:
String s = new Builder().append("ab").append(c) .toString();
對於String a = "a";String s = a + "b" + "c";,編譯器分析a爲引用變量,後面的"b" + "c"就不會做爲編譯時常量來運算了。至關於執行:
String s = new Builder().append(a).append("b") .append("c") .toString();
對於String b = "b";String s = "a" + b + "c";],這種形式的就沒辦法優化了,直接生成StringBuilder對象,而後調用三次 append()方法,即:
String s = new Builder().append("a").append(b) .append("c") .toString();
接着,咱們再看如下代碼:
其字符串的建立過程以下:首先在String Pool裏面查找查找是否有 "abc",若是有就直接使用,但這是本程序的第一條語句,故不存在一個對象"abc",因此要在String Pool中生成一個對象"abc",接下來,執行new String("abc")構造方法,new出來的對象都放在Heap裏面。在Heap裏又建立了一個"abc"的對象。這時內存裏就有兩個對象了,一個在String Pool 裏面,一個在Heap裏面。
問題2:當執行完語句(2)時,在內存裏面一共有幾個對象?它們是什麼?在什麼地方?
當執行完語句(2)時,在內存裏面一個對象也沒有建立。當咱們定義語句(2)的時候,若是咱們用字符串的常量值(字面值)給s2賦值的話,那麼首先JVM仍是從String Pool裏面去查找有沒有內容爲abc的這樣一個對象存在,咱們發現當咱們執行完語句(1)的時候,StringPool裏面已經存在了內容爲abc的對象,那麼就不會再在String Pool裏面去生成內容爲abc的字符串對象了。而是會使用已經存在String Pool裏面的內容爲abc的字符串對象,而且會將s2這個引用指向String Pool裏面的內容爲abc的字符串對象,s2存放的是String Pool裏面的內容爲abc的字符串對像的地址。也就是說當你使用String s2 = "abc",即便用字符串常量("abc")給定義的引用(str2)賦值的話,那麼它首先是在String Pool裏面去找有沒有內容爲abc的字符串對象存在,若是有的話,就不用建立新的對象,直接引用String Pool裏面已經存在的對象;若是沒有的話,就在 String Pool裏面去建立一個新的對象,接着將引用指向這個新建立的對象。因此,當執行完語句(2)時內存裏面一共有2個對象,它們的內容分別都是abc,在String Pool裏面一個內容abc的對象,在Heap裏面有一個內容爲abc的對象。
問題3:當執行完語句(3)時,在內存裏面一共有幾個對象?它們是什麼?在什麼地方?
當執行完語句(3)時,其執行過程是這樣的:它首先在String Pool裏面去查找有沒有內容爲abc的字符串對象存在,發現有這個對象存在,它就不去建立 一個新的對象。接着執行new...,只要在java裏面有關鍵字new存在,無論內容是否相同,都表示它將生成一個新的對象,new多少次,就生成多少個對象,並且新生成的對象都是在Heap裏面,因此它會在Heap裏面生成一個內容爲abc的對象,而且將它的地址賦給了引用s3,s3就指向剛在Heap裏面生成的內容爲abc的對象。因此,當執行完語句(3)時,內存裏面一共有3個對象,其中包含了在String Pool裏面一個內容爲abc的字符串對象和在Heap裏面包含了兩個內容爲abc的字符串對象。
問題4:當執行完語句(4)(5)(6)後,它們的結果分別是什麼?
在java裏面,對象用"=="永遠比較的是兩個對象的內存地址,換句話說,是比較"=="左右兩邊的兩個引用是否指向同一個對象。對於java裏面的8種原生數據類型來講,"=="比較的是它們的字面值是否是同樣的;對應用類型來講,比較的是它們的內存地址是否是同樣的。在語句(1)(2)(3)中,因爲s一、s二、s3指向不一樣的對象,它們的內存地址就不同,所以能夠說當執行完語句(4)(5)(6),它們返回的結果都是false。
問題5:當執行完語句(7)(8)(9)後,它們的結果分別是什麼?
首先,s1這個對象指向的是堆中第一次new...生成的對象,當調用 intern 方法時,若是String Pool已經包含一個等於此 String 對象的字符串(該對象由equals(Object)方法肯定),則返回指向String Pool中的字符串對象的引用。由於String Pool中有內容爲abc的對象,因此s1.intern()返回的是String Pool中的內容爲abc的字符串對象的內存地址,而s1倒是指向Heap上內容爲abc的字符串對象的引用。於是,兩個引用指向的對象不一樣,因此,s1 == s1.intern() 爲false,即語句(7)結果爲false。
對於s2.intern(),它仍是會首先檢查String Pool中是否有內容爲abc的對象,發現有,則將String Pool中內容爲abc的對象的地址賦給s2.intern()方法的返回值。由於s2和s2.intern()方法的返回值指向的是同一個對象,因此,s2 == s2.intern()的結果爲true,,即語句(8)結果爲true。
對於s1.intern(),它首先檢查String Pool中是否有內容爲abc的對象,發現有,則將String Pool中內容爲abc的對象的賦給s1.intern()方法的返回值。對於s2.intern(),首先檢查String Pool中是否有內容爲abc的對象,發現有,則將String Pool中內容爲abc的對象的地址賦給s2.intern()方法的返回值。由於二者返回的地址都指向同一個對象,因此,s1.intern() == s2.intern()的結果爲true,,便是語句(9)結果爲true。
所以,當執行完語句(7)(8)(9)後,它們的結果分別是false、true、true。
問題6:當執行完語句(13)(14) (15)(16)後,它們的結果分別是什麼?
hello == "hello"引用hello指向的對象就是String Pool中的「hello」,即語句(13)的結果爲true。
hello == "hel" + "lo"當加號兩邊都是常量值時,就會組成一個新的常量值"hello"在String Pool裏面,若是String Pool已經有相同內容的就不會再建立,則直接返回String Pool裏面的內容爲"hello"的字符串對象的內存地址,因此,hello == "hel" + "lo"結果爲true。
hello =="hel" + lo 當加號兩邊有一個不是常量值,會在堆裏面建立一個新的"hello"對象,一個在String Pool中,一個在Heap中,故輸出false 。
hel + lo 同上,輸出false。
所以,當執行完語句(7)(8)(9)後,它們的結果分別是true、true、false、false。
例程7:
1 String s1 = "abc";
2 String s2 = new String("abc");
3 String s3 = new String("abc");
4 s2.intern();//雖然執行了s2.intern(),但它的返回值沒有賦給s2
5 s3 = s3.intern();//把String Pool中「abc」的引用賦給s3
6 System.out.println(s1 == s2);
7 System.out.println(s1 == s2.intern());
8 System.out.println(s1 == s3);
9
10 運行結果以下:
11 false
12 true
13 true
結果說明:由於s2.intern()只是執行了,而沒有把String Pool中的「abc」的地址賦給s2,因此s1仍是指向String Pool中的「abc」,s2則指向Heap中的「abc」,對於s1 == s2,返回值爲false;對於s1 == s2.intern(),其中s2.intern()的返回值爲指向String Pool中「abc」的地址,故s2.intern()與s1指向同一個對象,於是返回true;由於s3 = s3.intern()把String Pool中的「abc」的地址賦給了s3,此時s1和s3指向的是同一對象,即String Pool中的「abc「,於是返回true。
例程8:
1 String s1 = "abc";
2 String s2 = new String("abc");
3 String s3 = "a" + new String("bc");
4 System.out.println(s1 == s2);
5 System.out.println(s1 == s3);
6 System.out.println(s2 == s3);
7
8 運行結果以下:
9 false
10 false
11 false
結果說明:
s1指向String Pool中的字符串對象「abc」,編譯期肯定;
s2指向Heap中的字符串對象「abc」,運行期肯定;
s3指向Heap中的另外一個字符串對象「abc」,運行期肯定。
注意:String s3 = "a" + new String("bc");等價於
String s3 = new StringBuilder().append("a").append(new String("bc")).toString();
思考:String s = "a" + new String("b") + "c";產生了那幾個對象?
解:等價於String s = new StringBuilder().append("a").append(new String("b")).append("c").toString(),會在String Pool中產生"a"、"b"、"c"三個對象,在Heap中產生"b"、"abc"兩個個對象。一共5個字符串對象。
例程9: