方法1:String s1 = "abc";
-
這一句話做了什么操作:
- 首先在常量池中查找"abc",如果沒有則在常量池創建該對象
- 在棧中創建s1的引用,將s1直接指向對象"abc"
- 因此在這里"abc"是常量池中的對象,如果聲明另一個String類型的對象引用,並將它指向對象"abc",則這兩個引用指向的是同一個常量池中的對象。
-
詳述
- 創建過程是,"abc"是字符串,是匿名的String對象。
- 當"abc"被聲明時,會先去字符串常量池中(位於堆內存中)查詢是夠存在該字符串了,如果存在將會返回常量池中的字符串引用地址,將此賦值給s。
- 如果沒有,將會把字符串"abc"存入到常量池中,然后進行再返回地址。
- 下次再使用該字符串時,直接返回該字符串再常量池中的地址即可。
- 這種創建對象的方式叫做享元模式,在Interger類中對[-128~127)之間的數值也是使用了該模式。
-
代碼示例:
String s1 = "abc";//"abc"是一個對象,將對象賦予類變量s1
String s2 = new String("abc");//這里是兩個對象,在內存中存在兩個,包括對象abc 和 new 出來的對象
String s3 = "abc"; //因為String類型數據是不可變的,所以‘abc’被放在了常量池中,這里的‘abc’ַ和s1的‘abc’是
//同一個常量abc對象,因此二者的內存地址是一樣的。
System.out.println(s1==s2);//false
System.out.println(s1==s3);//true 這是這號i
-
圖示辨析:
public class StringDemo {
public static void main(String[] args) {
String str = "Hello";
str = str + "World";
str += "!!!";
System.out.println(str);
}
}
public static void main(String[] args) {
String stra = "hello" ;
String strb = "hello" ;
String strc = "hello" ;
System.out.println(stra == strb);//true
System.out.println(stra == strc);//true
System.out.println(strb == strc);//true
}
}
方法2:String s = new String(“abc”);
- 凡是經過 new 創建出來的對象,都會在堆內存中分配新的空間,創建新的對象,所以s是String類新創建的對象
兩種初始化方法對比
String s = "aa";
s =s + "bb";
String s2 = "aabb";
s == s2;???
這個的結果是false,這時候s 和s2已經不是一樣的了,首先看 s2,s2指向的是常量池中的對象,這是確定的。所以盡管s的值和s2是一樣的,但是s指向的不是常量池的中的對象,而是一個新的new出來的對象。 解釋之前,先了解一下 + 這個符號,在字符串拼接里面,相當於+ 源碼大意為: (new StringBuffer()).append(s3).append(“bbb”).toString; 所以,這里的s指向的是一個新的對象。
總結: 在String的兩種聲明方式,直接賦予字符值的是,String對象引用獲取常量池中對象的地址,所以String聲明出來是不可以改變的。new String()出來的是在堆內存創建對象。如果要給每個對象中的String屬性賦予一個初始值,采用String s = ‘abc’方式,這樣創建的是常量池中的一個對象,其他對象是獲取這個常量的地址。要是new 則每次都要創建,加大內存消耗。還要注意,字符串拼接不要用+ ,會創建對象。
-
代碼示例
public class Demo1 {
@Test
public void test1() {
String s1 = "abc";//"abc"是一個對象,將對象賦予類變量s1
String s2 = new String("abc");//這里是兩個對象,在內存中存在兩個,包括對象abc 和 new 出來的對象
String s3 = "abc"; //因為String類型數據是不可變的,所以‘abc’被放在了常量池中,這里的‘abc’ַ和s1的‘abc’是
//同一個常量abc對象,因此二者的內存地址是一樣的。
System.out.println(s1==s2);//false
System.out.println(s1==s3);//true 這是這號i
//+ 源碼大意為: (new StringBuffer()).append(s3).append("bbb").toString;
//是新new出一個新的StringBuffer對象,
s3 = s3+"bbb";//這時候s3已經不指向"abc",源對象依舊存在,s3是新的string類型的對象
String s4 = "abcbbb";
String s5 = new String("abcbbb");
System.out.println(s3);
System.out.println(s3==s4);//false s3是一個新的String對象
System.out.println(s4=="abcbbb");//true 這個“abcbbb”屬於同一個常量池中的對象
System.out.println(s4==s5);//false 一個在常量池,一個是new的對象
}
}
intern()方法
-
概述
- intern()方法是能使一個位於堆中的字符串在運行期間動態地加入到字符串常量池中(字符串常量池的內容是程序啟動的時候就已經加載好了),如果字符串常量池中有該對象對應的字面量,則返回該字面量在字符串常量池中的引用,否則,創建復制一份該字面量到字符串常量池並返回它的引用。
-
代碼示例:
String s1 = "abc";
String s2 = new String("abc");
String s3 = new String("abc").intern();//s3其實是字符串常量"abc"
/*
s1是常量池中的對象,s2是堆中對象,是不同對象
*/
System.out.println(s1 == s2);//false
//兩者都是表示字符串常量abc,所以是true
System.out.println(s1 == s3);//true
//s3是常量池中的對象abc,s2是堆中對象,是不同對象
System.out.println(s2 == s3);
//都表示一個值abc
System.out.println(s1 == "abc"); //true
System.out.println(s3 == "abc"); //true
字符串內容不可變屬性分析
-
弊端:
- 字符串內容的更改,實際上改變的是字符串對象的引用過程,並且會伴隨有大量的垃圾出現,在實際開發中應該避免。
-
好處:
- 可以緩存hash值:
- String的值是不允許改變的,因此hash值就可以計算出來,緩存起來,不要要每次使用都計算。
- String pool的需要
- 如果一個String對象已經創建過了,那么將會從String pool中取得引用,只有String不可變,才能保證其他使用該對象是安全的。
- 線程安全
- String 不可變性天生具備線程安全,可以在多個線程中安全地使用。
- 安全性
- String經常作為參數,String的不可變特性可以保證參數不可變。列如在作為網絡連接參數的情況下,如果String可變,那么在網絡連接過程中,String被改變,改變String對象的哪一方以為現在連接的是其他主機,而實際情況卻不是。
- 可以緩存hash值: