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” .