1. String的不可變性
一旦一個String對象在內存中創建, 它將不可改變, 所有String類中方法並不是改變String對象自己, 而是重新創建一個新的String對象 .
第一行在常量池中創建一個”abc”對象,
第二行進行截取, 其實是重新創建一個對象, 然後讓a重新指向這個截取對象 .
但是原來”abc”對象並沒有變化 , 只是沒有引用指向它, 最後被垃圾回收 .
也就是”abc”, 一旦創建就不會被修改 . 這就是不可變性 .
查看String源碼 :
它的成員變量都是final, 尤其是value成員, 被final修飾, 表示這個變量一旦通過構造函數生成就不能被改變 .
所以String的不可變性並不是因爲類被聲明final(類被聲明final只能代表可不可被繼承), 真正決定不可變性, 是成員變量都被聲明爲final .
2. 代碼
*常量 : final String a = "abc", 被final修飾, 那麼a就是常量.
public static void main(String[] args) {
/**
* 1.常量池
* JVM存在一個常量池,其中保存很多String對象,並且可以被共享,提高效率
* 由於String類中成員是final, 它的值一旦創建不能被修改
* 字符串池由String類維護, 可以調用intern方法訪問字符串池 .
*/
// 字符串池創建一個對象
String s1 = "abc";
// 字符串池中存在"abc",所以這次不需要創建對象
String s2 = "abc";
// 所以兩個地址指向一致 .
System.out.println("s1 == s2:"+(s1 == s2)); // true
/**
* 2.new String("")
*/
// 創建兩個對象, 一個存在字符串池中, 一個存在堆中
String s3 = new String("abc");
// 池中已經存在"abc"對象, 所以只在堆中創建
String s4 = new String("abc");
// 所以s3和s4不相等,都在堆中,指向的內存區域不同
System.out.println("s3 == s4:"+(s3 == s4)); // false
// 一個在pool中另一個在堆中
System.out.println("s1 == s3:"+(s1 == s3)); // false
/**
* 3.常量的值在編譯時已經確定(優化)
* 這裏"ab"和"cd"都是常量, 因此"+"之後值在編譯時確定
* 等同於 str = "abcd"
*/
String s5 = "ab" + "cd";
String s6 = "abcd";
System.out.println("s5 == s6:"+(s5 == s6)); // true
/**
* 4.局部變量s7,s8存儲兩個拘留字符串對象的地址 .
* 那麼下面(s7+s8)原理 :
* 運行期JVM首先會在堆中創建一個StringBuilder對象,
* 然後利用s7指向的拘留字符串完成初始化
* 然後調用append方法完成對s8指向的字符串進行合併
* 然後調用toString方法在堆中創建一個String對象
* 最後將剛生成的String對象地址存放在s9中.
* s10存儲的是字符串池中"abcd"對應的地址.
* s9存儲是堆中"abcd"對應地址
*/
String s7 = "ab";
String s8 = "cd";
String s9 = s7 + s8;
String s10 = "abcd";
System.out.println("s9 == s10:"+(s9 == s10)); // false
/**
* 5.java編譯器對String+基本類型/常量, 當成常量表達式直接求值優化
* 運行期兩個string相加,會產生新的對象, 存在堆中
*/
String s11 = "b";
String s12 = "a" + s11;
String s13 = "ab";
//因爲s11是變量(被final修飾是常量),所以運行期纔會被解析
System.out.println("s12 == s13:"+(s12 == s13) ); // false
final String s14 = "b";
String s15 = "a" + s14;
String s16 = "ab";
System.out.println("s15 == s16:"+(s15 == s16)); // true
}
面試題:
曾經一個面試題 :
public static void main(String[] args) {
String a = "hello";
String b = "hel" + "lo";
String c = "hel" + new String("lo");
final String d = "lo";
String e = "lo";
String f = "hel" + d;
String g = "hel" + e;
System.out.println(a == b); // true b是常量相加
System.out.println(a == c); // false new String會重新新建對象
System.out.println(a == f); // true d是常量(被final修飾)
System.out.println(a == g); // false e是變量不會編譯期優化
}
3.以前疑惑
對於String a = “abc”, 我總在思考 a 到底是字符串常量還是變量 . 你說它是常量, 他卻沒有被final修飾, 如果說變量但是”abc”會被放在常量池中 .
最後別人一句話點醒了我, a 是變量, “abc”是常量 . 我以前老糾結在 a 上, 導致概念各種混亂 .
如果a要變成常量 , 需要被final修飾 , final String a = “abc” .