一、創建。
好了,知道String是非可變類以後,我們可以進一步瞭解String的構造方式了。創建一個Stirng對象,主要就有以下兩種方式:
java 代碼
java 代碼
1. String str1 = new String("abc");
2. Stirng str2 = "abc";
2. Stirng str2 = "abc";
java 代碼
1. String str1 = new String("abc"); //jvm 在堆上創建一個String對象
2.
3. //jvm 在strings pool中找不到值爲“abc”的字符串,因此
4. //在堆上創建一個String對象,並將該對象的引用加入至strings pool中
5. //此時堆上有兩個String對象
6. Stirng str2 = "abc";
7.
8. if(str1 == str2){
9. System.out.println("str1 == str2");
10. }else{
11. System.out.println("str1 != str2");
12. }
13. //打印結果是 str1 != str2,因爲它們是堆上兩個不同的對象
14.
15. String str3 = "abc";
16. //此時,jvm發現strings pool中已有“abc”對象了,因爲“abc”equels “abc”
17. //因此直接返回str2指向的對象給str3,也就是說str2和str3是指向同一個對象的引用
18. if(str2 == str3){
19. System.out.println("str2 == str3");
20. }else{
21. System.out.println("str2 != str3");
22. }
23. //打印結果爲 str2 == str3
2.
3. //jvm 在strings pool中找不到值爲“abc”的字符串,因此
4. //在堆上創建一個String對象,並將該對象的引用加入至strings pool中
5. //此時堆上有兩個String對象
6. Stirng str2 = "abc";
7.
8. if(str1 == str2){
9. System.out.println("str1 == str2");
10. }else{
11. System.out.println("str1 != str2");
12. }
13. //打印結果是 str1 != str2,因爲它們是堆上兩個不同的對象
14.
15. String str3 = "abc";
16. //此時,jvm發現strings pool中已有“abc”對象了,因爲“abc”equels “abc”
17. //因此直接返回str2指向的對象給str3,也就是說str2和str3是指向同一個對象的引用
18. if(str2 == str3){
19. System.out.println("str2 == str3");
20. }else{
21. System.out.println("str2 != str3");
22. }
23. //打印結果爲 str2 == str3
再看下面的例子:
java 代碼
java 代碼
1. String str1 = new String("abc"); //jvm 在堆上創建一個String對象
2.
3. str1 = str1.intern();
4. //程序顯式將str1放到strings pool中,intern運行過程是這樣的:首先查看strings pool
5. //有沒“abc”對象的引用,沒有,則在堆中新建一個對象,然後將新對象的引用加入至
6. //strings pool中。執行完該語句後,str1原來指向的String對象已經成爲垃圾對象了,隨時會
7. //被GC收集。
8.
9. //此時,jvm發現strings pool中已有“abc”對象了,因爲“abc”equels “abc”
10. //因此直接返回str1指向的對象給str2,也就是說str2和str1引用着同一個對象,
11. //此時,堆上的有效對象只有一個。
12. Stirng str2 = "abc";
13.
14. if(str1 == str2){
15. System.out.println("str1 == str2");
16. }else{
17. System.out.println("str1 != str2");
18. }
19. //打印結果是 str1 == str2
20.
2.
3. str1 = str1.intern();
4. //程序顯式將str1放到strings pool中,intern運行過程是這樣的:首先查看strings pool
5. //有沒“abc”對象的引用,沒有,則在堆中新建一個對象,然後將新對象的引用加入至
6. //strings pool中。執行完該語句後,str1原來指向的String對象已經成爲垃圾對象了,隨時會
7. //被GC收集。
8.
9. //此時,jvm發現strings pool中已有“abc”對象了,因爲“abc”equels “abc”
10. //因此直接返回str1指向的對象給str2,也就是說str2和str1引用着同一個對象,
11. //此時,堆上的有效對象只有一個。
12. Stirng str2 = "abc";
13.
14. if(str1 == str2){
15. System.out.println("str1 == str2");
16. }else{
17. System.out.println("str1 != str2");
18. }
19. //打印結果是 str1 == str2
20.
爲什麼jvm可以這樣處理String對象呢?就是因爲String的非可變性。既然所引用的對象一旦創建就永不更改,那麼多個引用共用一個對象時互不影響。
二、串接(Concatenation)。
三、String的長度
我們可以使用串接操作符得到一個長度更長的字符串,那麼,String對象最多能容納多少字符呢?查看String的源代碼我們可以得知類String中是使用域 count 來記錄對象字符的數量,而count 的類型爲 int,因此,我們可以推測最長的長度爲 2^32,也就是4G。
不過,我們在編寫源代碼的時候,如果使用 Sting str = "aaaa";的形式定義一個字符串,那麼雙引號裏面的ASCII字符最多只能有 65534 個。爲什麼呢?因爲在class文件的規範中, CONSTANT_Utf8_info表中使用一個16位的無符號整數來記錄字符串的長度的,最多能表示 65536個字節,而java class 文件是使用一種變體UTF-8格式來存放字符的,null值使用兩個字節來表示,因此只剩下 65536- 2 = 65534個字節。也正是變體UTF-8的原因,如果字符串中含有中文等非ASCII字符,那麼雙引號中字符的數量會更少(一箇中文字符佔用三個字節)。如果超出這個數量,在編譯的時候編譯器會報錯。
不過,我們在編寫源代碼的時候,如果使用 Sting str = "aaaa";的形式定義一個字符串,那麼雙引號裏面的ASCII字符最多只能有 65534 個。爲什麼呢?因爲在class文件的規範中, CONSTANT_Utf8_info表中使用一個16位的無符號整數來記錄字符串的長度的,最多能表示 65536個字節,而java class 文件是使用一種變體UTF-8格式來存放字符的,null值使用兩個字節來表示,因此只剩下 65536- 2 = 65534個字節。也正是變體UTF-8的原因,如果字符串中含有中文等非ASCII字符,那麼雙引號中字符的數量會更少(一箇中文字符佔用三個字節)。如果超出這個數量,在編譯的時候編譯器會報錯。
對String類的理解網上有很多的資料,個人覺得很多都是錯誤的,錯誤的解釋危害性更大。所以爲了解釋清楚,就來看代碼,代碼運行出的結果纔是最具有說服力的。說明:以下都是個人的理解,歡迎指正。
Java代碼 複製代碼
Java代碼 複製代碼
1. public class StringTest {
2. public static void main(String[] args) {
3. String str1 = new String("abc");
4.
5. String str2 = "abc";
6.
7. if (str1 == str2) {
8. System.out.println("str1 == str2");
9. } else {
10. System.out.println("str1 != str2");
11. }
12.
13. String str3 = "abc";
14. if (str2 == str3) {
15. System.out.println("str2 == str3");
16. } else {
17. System.out.println("str2 != str3");
18. }
19.
20. str1 = str1.intern();
21.
22. if (str1 == str2) {
23. System.out.println("str1 == str2");
24. } else {
25. System.out.println("str1 != str2");
26. }
27.
28. String str4 = new String("abc");
29. str4 = str4.intern();
30.
31. if (str1 == str4) {
32. System.out.println("str1 == str4");
33. } else {
34. System.out.println("str1 != str4");
35. }
36. }
37.
38. }
2. public static void main(String[] args) {
3. String str1 = new String("abc");
4.
5. String str2 = "abc";
6.
7. if (str1 == str2) {
8. System.out.println("str1 == str2");
9. } else {
10. System.out.println("str1 != str2");
11. }
12.
13. String str3 = "abc";
14. if (str2 == str3) {
15. System.out.println("str2 == str3");
16. } else {
17. System.out.println("str2 != str3");
18. }
19.
20. str1 = str1.intern();
21.
22. if (str1 == str2) {
23. System.out.println("str1 == str2");
24. } else {
25. System.out.println("str1 != str2");
26. }
27.
28. String str4 = new String("abc");
29. str4 = str4.intern();
30.
31. if (str1 == str4) {
32. System.out.println("str1 == str4");
33. } else {
34. System.out.println("str1 != str4");
35. }
36. }
37.
38. }
運行結果:
str1 != str2
str2 == str3
str1 == str2
str1 == str4
str2 == str3
str1 == str2
str1 == str4
看看運行結果後,給出我自己的理解說明:
1、String有一個所謂的String pool,這是一個什麼東西呢,我理解是它是堆(heap)上特殊的一個空間(我叫它特殊堆)。注意它也是在堆上。
2、產生String類型的對象有兩種方法,那麼這兩種方法有什麼區別呢?我的理解是String str = “abc”是先用equals方法(String類覆蓋了equals方法)判斷這個特殊堆(String pool)是否有abc,有則將原來在棧中指向abc的引用賦值給str,否則就在這個特殊堆(String pool)上創建一個abc對象。String str2 = new String("abc")則是在普通堆上創建abc對象。所以str和str2是指向不同的對象,它們是不同的。
3、String有個intern()方法,這個方法是個本地方法,當用String str2 = new String("abc")來創建對象時,它相當於告訴JVM,我這個abc對象是放在特殊堆(String pool)上的。所以第三個打印結果是相等的。
4、需要注意的一點:String是final類,它是恆定類,一旦創建就無法改變,所以用intern()方法是重新在String pool中創建了一個新的對象。
關於String更詳細的講解,詳見《跟我學Java26日通》視頻教程