String的實例化與static final修飾符


String兩種實例化方式

一種是通過雙引號直接賦值的方式,另外一種是使用標准的new調用構造方法完成實例化。如下:
  String str = "abcd";
  String str = new String("1234);

第一種方法:
  使用直接賦值后,只要是以后聲明的字符串內容相同,則不會再開辟新的內存空間。對於String的以上操作,在java中稱為共享設計。這種設計思路是,在java中形成一個字符串對象池,在這個字符串對象中保存多個字符串對象,新實例化的對象如果已經在池中定義了,則不再重新定義,而從池中直接取出繼續使用。String就是因為采用了這樣的設計,所以當內容重復時,會將對象指向已存在的實例空間。

  一個雙引號包含字符串就是一個String類的匿名對象,但是這種方式使用String不一定創建新對象。在執行到這個字符串的語句時,如String a = "123",JVM會先到常量池里查找,如果有的話返回常量池里的這個實例的引用,否則的話創建一個新實例並置入常量池里。

第二種方法:
  使用new關鍵字,不管如何都會再開辟一個新的空間。
  new創建字符串時首先查看池中是否有相同值的字符串,如果有,則拷貝一份到堆中,然后返回堆中的地址;如果池中沒有,則在堆中創建一份,然后返回堆中的地址(注意,此時不需要從堆中復制到池中,否則,將使得堆中的字符串永遠是池中的子集,導致浪費池的空間)!

 

String實例化的時機

(1)單獨使用""引號創建的字符串都是常量,編譯期就已經確定存儲到String Pool中;
(2)使用new String("")創建的對象會存儲到堆區(heap)中,是運行期新創建的;
(3)使用只包含常量的字符串連接符如"aa" + "aa"創建的也是常量,編譯期就能確定,已經確定存儲到String Pool中;
(4)使用包含變量的字符串連接符如"aa" + s1創建的對象是運行期才創建的,存儲在堆區(heap)中;

  注意:上面第(3)句話,編譯后合並的字符串會保存在JVM的字符串池中,而不是再生成的class文件中把字符串合並。
  String s = "a" + "b" + "c"; 創建的是一個對象,而不是是四個對象,在字符串常量池中只生成一個字符串對象

 

字符串池的優缺點

  字符串池的優點就是避免了相同內容的字符串的創建,節省了內存,省去了創建相同字符串的時間,同時提升了性能;另一方面,字符串池的缺點就是犧牲了JVM在常量池中遍歷對象所需要的時間,不過其時間成本相比而言比較低。

 

static final修飾的字符串好嗎?

工作后發現,大型的項目里,常常會見到定義字符串使用 private static final String = "abc" 的方式。這種方式有好處嗎?

  首先使用直接賦值的字串的方式,字符串會在編譯期生成在字符串池中。

  然后final標記的變量(成員變量或局部變量)即成為常量,只能賦值一次。它應該不影響內存的分配。(查看資料多了,說法不一,在下對此也有點懷疑了,如果final影響內存分配,煩請各位大俠告知)

  最后看static修飾符:
        static修飾符能夠與屬性、方法和內部類一起使用,表示靜態的。類中的靜態變量和靜態方法能夠與類名一起使用,不需要創建一個類的對象來訪問該類的靜態成員,所以,static修飾的變量又稱作“類變量”。
  “類變量”屬於類的成員,類的成員是被儲存在堆內存里面的。一個類中,一個static變量只會有一個內存空間,即使有多個類實例,但這些類實例中的這個static變量會共享同一個內存空間。

  static修飾的String,會在堆內存中復制一份常量池中的值。所以調用 static final String 變量,實際上是直接調用堆內存的地址,不會遍歷字符串池中的對象,節省了遍歷時間

所以使用static final修飾的字符串還是有好處的。

 

代碼測試

 1 public class Test
 2 {
 3     public static final String A="ab";
 4     public static final String B="cd";
 5 
 6     public static final String C;
 7     public static final String D;
 8     static{
 9         C = "ab";
10         D = "cd";
11     }
12     public static void main(String[] args) {
13         String t = "abcd";//指向池
14 
15         String s1 = "ab";//指向池
16         String s2 = "cd";//指向池
17 
18         String s = s1+s2;//指向堆
19         System.out.println(s==t);//false
20 
21         String ss = "ab"+s2;//指向堆
22         System.out.println(ss==t);//false
23 
24         String sss = "ab"+"cd";//指向池
25         System.out.println(sss==t);//true
26 
27         String ssss = A+B;//指向池
28         System.out.println(ssss==t);//true
29 
30         System.out.println((C+D)==t);//false
31     }
32 
33 }

 

字符串對象可以存放在兩個地方,字符串池(pool)和堆,編譯期確定如何給一個引用變量賦值

  1. String s="abc";這種形式決定將從pool中尋找內容相同的字符串並返回地址給s,pool中沒有就會在pool中新建並返回地址給s
  2. String s = new String("abc");這種形式決定運行期將在堆上新建字符串對象並返回給s,但這個對象不會加入到pool中
  3. String s=s1+s2;s1和s2都是變量,這種形式決定將在堆上創建s1和s2(即便s1和s2指向的對象在池中已經存在,也會將值拷貝到對象創建新對象),然后創建s1+s2並賦給s
  4. String s = "ab"+"cd";同1),都是來自於池
  5. String s = "ab"+s1;類似3)
  6. String s = S1+S2;S1和S2是常量,常量只能賦值一次,S1,S2如果在聲明的地方就賦值,那么這個值在編譯期就是確定的,后面無法更改,S1+S2在執行前可確定S1/S2已經在池中存在,當然在池中進行,所以s指向pool;但是若S1,S2如果是實例常量在構造器中賦值,或是類常量在靜態塊中賦值,S1+S2無法確定二者皆來自於池,於是在堆上進行

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM