Java關於String創建和其不可變的一些理解

一、String是不可變的

 private final byte[] value;//String源碼

通過String的原碼我們可以知道String的儲存本質是一個byte數組,在Java之前的版本中使用過char但是後來進行了一些改變成了現在的byte。其前面有着final修飾說明了其引用是不可變的,這裏限定了String是不可變的。
那麼爲什麼要將String設置成不可變的量呢,原因有很多,其中最主要的則是安全和效率。
將String設爲不可變讓String的常量池成爲可能,每一個String對象在創建時都可以在常量池中創建一個String來緩存其內容和hash碼,這樣以後再有同樣的創建時就可以不必重新計算hash碼只用在常量池中檢索並複製過來就行(String中每一個串的值對應一個hash碼)。

String s1="abc";
String s2="abc";
System.out.println(s1==s2);//True

從這個例子我們就可以看出來s1和s2指向的同一段地址,都是指向其常量池中緩存的內容,這樣就節約了空間也加快了效率。倘若String是可變的那麼上面這段代碼這樣的就不可能實現,因爲萬一其中一個改變了另外一個就會受到影響。

String s1=new String("abc");
String s2=new String("abc");
System.out.println(s1==s2);//false
System.out.println(s1.hashCode()==s2.hashCode());//true

上面這段代碼中創建值爲"abc"的String對象時則是會檢索常量池中是否存在一個值爲abc的串,如果有則直接複製過來,節約了很多時間,提高了效率。但是這個只是複製並非讓引用指向同一個對象,所以他們的引用不相等,但是他們的hash碼是相等的。
之後就是安全性,String對象不可改變對與多線程操作時是安全的,不會因爲一個線程修改數據而出現問題,同時其參數傳遞是安全的。

static void change(String s) {
		s=s+"d";
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String s1="abc";
		change(s1);
		System.out.println(s1);//abc
}

上述代碼可以看出在傳參後s修改了String的值,在原來的s1中值並沒有改變,其本質就是創建了一個全新的對象值爲abcd並讓引用s指向它。

	static String change(String s) {
		s=s+"d";
		return s;
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String s1="abc";
		String s=change(s1);
		System.out.println(s1==s);
}

從這段代碼中我們可以更明確的發現s1和已經不是同一個對象了,所有的改變都不會作用在原來的對象上,這樣也就保護了原來對象的值,比如密碼由String來保存,就不用擔心會被黑客修改。
當然Java爲了方便操作,引入了StringBuffer和StringBuilder來創建可以修改的串,用append方法來延長串。

二、String的創建機制

上面提到String在創建時會在常量池先創建一個串,再複製到對象所在的堆地址中,我們通過一段代碼來看一下:

String s1="abc";
String s2=new String("abc");
String s3=new String("abc");
String s4="abc";
String s5="a"+"bc";
String s6="a"+new String("bc");
String s7="a".concat("bc");
System.out.println(s1==s4);//True
System.out.println(s1==s2);//false
System.out.println(s2==s3);//false
System.out.println(s3==s4);//false
System.out.println(s1==s5);//true
System.out.println(s1==s6);//false
System.out.println(s1==s7);//false
System.out.println(s1==s2.intern());//true
System.out.println(s2.intern()==s3.intern());//true

s1創建時在常量池創建一個值爲abc的串並指向它,s2和s3分別在堆中創建了兩個不同的但是值均爲abc的串。s4創建時先檢索了常量池存在abc就直接指向常量池中對象。s5這種方式會被JVM優化爲與s4相同的方式,在JVM對其操作時會自動把“+”去掉,來在常量池中尋找值爲abc的串並指向。而s6則由於其中有一個堆中對象,所以這種操作會在堆中創建一個值爲bc的無名對象並且再創建一個值爲abc的對象,當然也是在堆中,所以引用和s1也不相同。s7與s6類似,concat方法也算創建一個全新的對象來進行賦值的。intern方法用來返回一個串在常量池中相應對象的引用,如果沒有則會新建一個常量池對象。所以只要值相同的串其intern方法返回值均相同。
我們注意到我們每次用new創建一個新的串都會在常量池中創建一個串,所以String s=new String("abc");創建的對象可能是一個也可能是兩個,因爲這一句代碼並不能知道常量池中到底有沒有一個值爲abc的串。
那麼問題又來了,如果是這樣的話,那爲什麼intern方法要去考慮常量池沒有這個串的情況呢,只是爲了保險起見麼?當然並不是。

String s11=new String("a");
String s12=new String("bc");
String s13=s11+s12;
System.out.println(s13);//abc

看這段代碼,倘若前面並沒有創建值爲abc的對象,那麼這段代碼便沒有在常量池中創建值爲abc的值。因爲s13的abc是由s11和s12連接而來,所以就沒有產生值爲abc的常量。

String s1="abc";
String s8="abc".concat("");
String s9=s1.concat("");
System.out.println(s1==s8);//true
System.out.println(s1==s9);//true

另外我們從源碼中可以看出concat方法並非一定會創建新的對象,當連接對象爲""時返回的是this,也就是並沒有新對象產生。

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