細節問題1:String類與字符串常量池?

細節問題1:String類與字符串常量池

1、String對象創建方式

String str1 = "abcd";
String str2 = new String("abcd");//abcd 也會存進常量池裏邊,同時堆中也創建一個對象,並返回地址引用
System.out.println(str1==str2);//false

這兩種不同的創建方法是有差別的,第一種方式是在常量池中拿對象,第二種方式是直接在堆內存空間創建一個新的對象。
只要使用new方法,便需要創建新的對象。

2、String使用—連接表達式 +

重載“+”

在Java中,唯一被重載的運算符就是用於String的“+”與“+=”。除此之外,Java不允許程序員重載其他的運算符。

(1)只有使用引號包含文本的方式創建的String對象之間使用“+”連接產生的新對象纔會被加入字符串池中。
(2)對於所有包含new方式新建對象(包括null)的“+”連接表達式,它所產生的新對象都不會被加入字符串池中。

  String str1 = "str";//字符串常量池中開闢對象,返回地址引用
  String str2 = "ing";//字符串常量池中開闢對象,返回地址引用
  
  String str3 = "str" + "ing";//字符串常量池中開闢新對象,返回地址引用
  String str4 = str1 + str2;//堆中創建新對象,不存進字符串常量池,故於str3地址不同
  System.out.println(str3 == str4);//false
  
  String str5 = "string";//因爲str3=string。被存進字符串常量池,這裏直接拿來用
  System.out.println(str3 == str5);//true

java基礎:字符串的拼接

1、例子1

public class StringPoolTest {
    public static final String a ="ab";
    public static final String b ="ab";
    public static final String c ;
    public static final String d ;
    static{
        c="cd";
        d = "cd";
    }

    public static void main(String[] args) {
        String s0 = a+b;//s是由a,b常量"+"鏈接的常量,存進字符串常量池
        String t0 = "abab";
        System.out.println((s0==t0)+" zero");//true
        
//c,d雖然被定義爲常量,但是由於是用靜態代碼塊進行初始化,必須是在類加載才能確認其值,所以就相當於變量,那麼s1自然是不會進入常量池
        String s1 = c+d;
        String t1 = "cdcd";
        System.out.println((s1==t1)+" first");//false

在這裏插入圖片描述

第一種:A和B都是常量,值是固定的,因此s0的值也是固定的,它在類被編譯時就已經確定了。也就是說:String s0=A+B; 等同於:String s0=“ab”+“ab”;

第二種:C和D雖然被定義爲常量,但是它們都沒有馬上被賦值。在運算出s的值之前,他們何時被賦值,以及被賦予什麼樣的值,都是個變數。因此C和D在被賦值之前,性質類似於一個變量。那麼s1就不能在編譯期被確定,而只能在運行時被創建了。

2、String s1 = new String(“xyz”); 創建了幾個對象?

考慮類加載階段和實際執行時。
(1)類加載對一個類只會進行一次。"xyz"在類加載時就已經創建並駐留了(如果該類被加載之前已經有"xyz"字符串被駐留過則不需要重複創建用於駐留的"xyz"實例)。駐留的字符串是放在全局共享的字符串常量池中的。
(2)在這段代碼後續被運行的時候,"xyz"字面量對應的String實例已經固定了,不會再被重複創建。所以這段代碼將常量池中的對象複製一份放到heap中,並且把heap中的這個對象的引用交給s1 持有。
這條語句創建了2個對象。

3、String.intern()

運行時常量池相對於CLass文件常量池的另外一個重要特徵是具備動態性,Java語言並不要求常量一定只有編譯期才能產生,也就是並非預置入CLass文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的就是String類的intern()方法。
String的intern()方法會查找在常量池中是否存在一份equal相等的字符串,如果有則返回該字符串的引用,如果沒有則添加自己的字符串進入常量池。

public static void main(String[] args) {    
      String s1 = new String("計算機");//存進常量池,同時堆中創建一份,返回地址引用
      String s2 = s1.intern();//intern就會去字符串常量池中尋找有沒有s1,有就返回地址,沒有就創建並返回。
      String s3 = "計算機";
      System.out.println("s1 == s2? " + (s1 == s2));
      System.out.println("s3 == s2? " + (s3 == s2));
  }
 

結果 :

s1 == s2? false
s3 == s2? true

4、字符串比較更豐富的一個例子

class Other1 { static String hello = "Hello"; }
public class Test {
 public static void main(String[] args) {   
      String hello = "Hello", lo = "lo";
      System.out.println((hello == "Hello") + " 1");
      System.out.println((Other1.hello == hello) + " 2");
   	  System.out.println((other.Other.hello == hello) + " 3");//只要是同一份內容,字符常量池就只有一份
      System.out.println((hello == ("Hel"+"lo")) + " 4");//加號得到“hello”本來就在常量池中,故指向地址一致
      System.out.println((hello == ("Hel"+lo)) + " 5");//lo是變量,而且不進入常量池,是在堆開闢空間,地址不一樣
      System.out.println((hello == ("Hel"+lo).intern())+" 6");//使用intern(),檢查常量池有沒有,沒有就創建,顯然是有的,就是hello
 }   
}

package other;
public class Other { public static String hello = "Hello"; }
 

在這裏插入圖片描述

在同包同類下,引用自同一String對象.
在同包不同類下,引用自同一String對象.
在不同包不同類下,依然引用自同一String對象.
在編譯成.class時能夠識別爲同一字符串的,自動優化成常量,引用自同一String對象.
在運行時創建的字符串具有獨立的內存地址,所以不引用自同一String對象.

參照博客以及深度好文(贊一個!):字符串常量池、class常量池和運行時常量池

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章