天天看點

String建立了幾個對象

我們首先來看一段代碼:

Java代碼

緊接着這段代碼之後的往往是這個問題,那就是這行代碼究竟建立了幾個String對象呢?相信大家對這道題并不陌生,答案也是衆所周知的,2個。接下來我們就從這道題展開,一起回顧一下與建立String對象相關的一些JAVA知識。

我們可以把上面這行代碼分成String str、=、"abc"和new String()四部分來看待。String str隻是定義了一個名為str的String類型的變量,是以它并沒有建立對象;=是對變量str進行初始化,将某個對象的引用(或者叫句柄)指派給它,顯然也沒有建立對象;現在隻剩下new String(“abc”)了。那麼,new String(“abc”)為什麼又能被看成"abc"和new String()呢?我們來看一下被我們調用了的String的構造器:

Java代碼

public String(String original) {   
  //other code ...   
}   
           

大家都知道,我們常用的建立一個類的執行個體(對象)的方法有以下兩種:

使用new建立對象。

調用Class類的newInstance方法,利用反射機制建立對象。

我們正是使用new調用了String類的上面那個構造器方法建立了一個對象,并将它的引用指派給了str變量。同時我們注意到,被調用的構造器方法接受的參數也是一個String對象,這個對象正是"abc"。由此我們又要引入另外一種建立String對象的方式的讨論——引号内包含文本。

這種方式是String特有的,并且它與new的方式存在很大差別。

Java代碼

毫無疑問,這行代碼建立了一個String對象。

Java代碼

String a="abc";   
String b="abc";   
           

那這裡呢?答案還是一個。

Java代碼

再看看這裡呢?答案是三個。有點奇怪嗎?說到這裡,我們就需要引入對字元串池相關知識的回顧了。

在JAVA虛拟機(JVM)中存在着一個字元串池,其中儲存着很多String對象,并且可以被共享使用,是以它提高了效率。由于String類是final的,它的值一經建立就不可改變,是以我們不用擔心String對象共享而帶來程式的混亂。字元串池由String類維護,我們可以調用intern()方法來通路字元串池。

我們再回頭看看String a=“abc”;,這行代碼被執行的時候,JAVA虛拟機首先在字元串池中查找是否已經存在了值為"abc"的這麼一個對象,它的判斷依據是String類equals(Object obj)方法的傳回值。如果有,則不再建立新的對象,直接傳回已存在對象的引用;如果沒有,則先建立這個對象,然後把它加入到字元串池中,再将它的引用傳回。是以,我們不難了解前面三個例子中頭兩個例子為什麼是這個答案了。

對于第三個例子:

Java代碼

“ab"和"cd"分别建立了一個對象,它們經過“+”連接配接後又建立了一個對象"abcd”,是以一共三個,并且它們都被儲存在字元串池裡了。

現在問題又來了,是不是所有經過“+”連接配接後得到的字元串都會被添加到字元串池中呢?我們都知道“==”可以用來比較兩個變量,它有以下兩種情況:

如果比較的是兩個基本類型(char,byte,short,int,long,float,double,boolean),則是判斷它們的值是否相等。

如果表較的是兩個對象變量,則是判斷它們的引用是否指向同一個對象。

下面我們就用“==”來做幾個測試。為了便于說明,我們把指向字元串池中已經存在的對象也視為該對象被加入了字元串池:

Java代碼

public class StringTest {   
  public static void main(String[] args) {   
  String a = "ab";// 建立了一個對象,并加入字元串池中   
  System.out.println("String a = "ab";");   
  String b = "cd";// 建立了一個對象,并加入字元串池中   
  System.out.println("String b = "cd";");   
  String c = "abcd";// 建立了一個對象,并加入字元串池中   
   
  String d = "ab" + "cd";   
  // 如果d和c指向了同一個對象,則說明d也被加入了字元串池   
  if (d == c) {   
  System.out.println(""ab"+"cd" 建立的對象 "加入了" 字元串池中");   
  }   
  // 如果d和c沒有指向了同一個對象,則說明d沒有被加入字元串池   
  else {   
  System.out.println(""ab"+"cd" 建立的對象 "沒加入" 字元串池中");   
  }   
   
  String e = a + "cd";   
  // 如果e和c指向了同一個對象,則說明e也被加入了字元串池   
  if (e == c) {   
  System.out.println(" a +"cd" 建立的對象 "加入了" 字元串池中");   
  }   
  // 如果e和c沒有指向了同一個對象,則說明e沒有被加入字元串池   
  else {   
  System.out.println(" a +"cd" 建立的對象 "沒加入" 字元串池中");   
  }   
   
  String f = "ab" + b;   
  // 如果f和c指向了同一個對象,則說明f也被加入了字元串池   
  if (f == c) {   
  System.out.println(""ab"+ b 建立的對象 "加入了" 字元串池中");   
  }   
  // 如果f和c沒有指向了同一個對象,則說明f沒有被加入字元串池   
  else {   
  System.out.println(""ab"+ b 建立的對象 "沒加入" 字元串池中");   
  }   
   
  String g = a + b;   
  // 如果g和c指向了同一個對象,則說明g也被加入了字元串池   
  if (g == c) {   
  System.out.println(" a + b 建立的對象 "加入了" 字元串池中");   
  }   
  // 如果g和c沒有指向了同一個對象,則說明g沒有被加入字元串池   
  else {   
  System.out.println(" a + b 建立的對象 "沒加入" 字元串池中");   
  }   
  }   
}   
           

運作結果如下:

String a = "ab";  
String b = "cd";  
"ab"+"cd" 建立的對象 "加入了" 字元串池中  
a +"cd" 建立的對象 "沒加入" 字元串池中  
"ab"+ b 建立的對象 "沒加入" 字元串池中  
a + b 建立的對象 "沒加入" 字元串池中  
           

從上面的結果中我們不難看出,隻有使用引号包含文本的方式建立的String對象之間使用“+”連接配接産生的新對象才會被加入字元串池中。對于所有包含new方式建立對象(包括null)的“+”連接配接表達式,它所産生的新對象都不會被加入字元串池中,對此我們不再贅述。是以我們提倡大家用引号包含文本的方式來建立String對象以提高效率,實際上這也是我們在程式設計中常采用的。

接下來我們再來看看intern()方法,它的定義如下:

Java代碼

這是一個本地方法。在調用這個方法時,JAVA虛拟機首先檢查字元串池中是否已經存在與該對象值相等對象存在,如果有則傳回字元串池中對象的引用;如果沒有,則先在字元串池中建立一個相同值的String對象,然後再将它的引用傳回。

我們來看這段代碼:

Java代碼

public class StringInternTest {   
  public static void main(String[] args) {   
  // 使用char數組來初始化a,避免在a被建立之前字元串池中已經存在了值為"abcd"的對象   
  String a = new String(new char[] { 'a', 'b', 'c', 'd' });   
  String b = a.intern();   
  if (b == a) {   
  System.out.println("b被加入了字元串池中,沒有建立對象");   
  } else {   
  System.out.println("b沒被加入字元串池中,建立了對象");   
  }   
  }   
}   
           

運作結果:

b沒被加入字元串池中,建立了對象  
           

如果String類的intern()方法在沒有找到相同值的對象時,是把目前對象加入字元串池中,然後傳回它的引用的話,那麼b和a指向的就是同一個對象;否則b指向的對象就是JAVA虛拟機在字元串池中建立的,隻是它的值與a相同罷了。上面這段代碼的運作結果恰恰印證了這一點。

最後我們再來說說String對象在JAVA虛拟機(JVM)中的存儲,以及字元串池與堆(heap)和棧(stack)的關系。我們首先回顧一下堆和棧的差別:

棧(stack):主要儲存基本類型(或者叫内置類型)(char、byte、short、int、long、float、double、boolean)和對象的引用,資料可以共享,速度僅次于寄存器(register),快于堆。

堆(heap):用于存儲對象。

我們檢視String類的源碼就會發現,它有一個value屬性,儲存着String對象的值,類型是char[],這也正說明了字元串就是字元的序列。

當執行String a=“abc”;時,JAVA虛拟機會在棧中建立三個char型的值’a’、‘b’和’c’,然後在堆中建立一個String對象,它的值(value)是剛才在棧中建立的三個char型值組成的數組{‘a’,‘b’,‘c’},最後這個新建立的String對象會被添加到字元串池中。如果我們接着執行String b=new String(“abc”);代碼,由于"abc"已經被建立并儲存于字元串池中,是以JAVA虛拟機隻會在堆中新建立一個String對象,但是它的值(value)是共享前一行代碼執行時在棧中建立的三個char型值值’a’、‘b’和’c’。

補充

系統記憶體一般情bai況來說分為四個

heap 堆 放 對象 也就是new 出來的東du西

stack 棧 放局部變量值

static segment 靜态區 用來放 靜态變量 和字元dao串常量

data segement 代碼區 用來放代碼的

如果 一個字元串是 String s = “abc”;它放在棧裡

如果一個字元串 用建立對象的方式 String s = new String(“abc”);

那它是放在了 堆裡 而如果單純的 一個 “abc” 這個輸入字元串常量 是放在static segement裡

說到這裡,我們對于篇首提出的String str=new String(“abc”)為什麼是建立了兩個對象這個問題就已經相當明了了