最近学习JAVA时看到一句话”注意:String类是不可改变的,所以你一旦创建了String对象,那它的值就无法改变了“,有些不解,查了查资料,感觉值得注意。
查看JAVA API文档中文版,找到类String,“字符串是常量;它们的值在创建之后不能改变",
public final class String
extends Object
implements Serializable, Comparable<String>, CharSequence
Final类不能被继承,没有类能够继承final类的任何特性,其方法也自然是final,因此无法修改。
在分析之前,需要先说明字符串常量池(String pool)的概念。
字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域,当创建一个String对象时,首先会在String pool查找是否存有该字符串值,若存在则不会创建一个新的对象,而是直接引用已经存在的对象,若不存在则会创建该字符串对象,并存放于String pool。
以下为例:
String str = "abc";
str = "def";
执行String str = "abc"后,首先查找String pool是否已存在字符串“abc”,若存在则变量str指向该存储空间,若不存在则创建并存放于String pool。
执行str = "def"后,str不再引用对象"def",而是查找String pool是否已存在字符串“def”,若存在则变量str指向该存储空间,若不存在则创建并存放于String pool。原对象”abc“因为没有被引用,成为垃圾数据,等待回收。
不可变字符串String的另一层含义在于,创建"abc"后将一直不变存在于堆内存直到回收,即使在堆内存申请"abcd",也不会在"abc"加一个‘d’,而是再申请一个"abcd"。
有意思的是,若再申明一个String str2 = "def",由于”def“在String pool已经存在,变量str2也会指向该对象,也就是说变量str和str2指向同一存储空间。这都是基于字符串常量池实现的。
为了验证以上分析,可运行以下程序:
<span style="font-size:14px;"><span style="font-size:18px;">public class MyCode3{
public static void main(String[] args){
String str1 = new String("abc");
String str2 = "abc";
String str3 = "abc";
System.out.println(str1==str2);
System.out.println(str2==str3);
}
}</span></span>
运行结果:
false
true
注:JAVA中用"=="判断String类的内存地址是否相同,用equals()判断String类的字符串值是否相等。
结果说明,str2和str3确实指向同一存储空间。
需要注意的是
//JVM在heap中(不是String Pool)创建一个String对象,然后将该对象的引用返回给用户。
String str1 = "abc";
//首先查找String pool是否已存在字符串“abc”,若存在则变量str指向该存储空间,若不存在则创建并存放于String pool。
String str2 = new String("abc");
详请见http://wenku.baidu.com/view/d223a71714791711cc791737.html
JAVA API文档中说明了String类包括的方法:检查序列的单个字符;比较字符串;搜索字符串;提取子字符串;大小写转换。所有这些函数最后的结果都是在堆内存重新申请一个存储空间保存结果,变量再指向该存储空间,而不会改变之前的对象,之前引用的存储空间中的数据成为垃圾数据,等待系统回收。这一点可以分析String方法源码得出,String方法源码在JAVA安装目录D:\Program Files\Java\jdk1.8.0_65\src\java\lang\String.java中,如concat方法:
<span style="font-size:14px;"><span style="font-size:18px;"> public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}</span></span>
将String类设计成不可变类型的优点:
1、安全,String类对象永远不会变,在多线程情况下不会被某线程改变;
2、高效,对于一个初始化的不可变对象,以后只需传地址即可。
3、节约空间,可以构建字符串常量池,使不同的字符串变量指向同一个字符串,节约heap空间。
缺点:在内存中会产生很多副本,相比与StringBuffer消耗更多内存。
PS,StringBuffer和StringBuilder类是可变的字符串,该类方法在原来的对象之上进行改变,不会产生新的未使用对象。
至于为何在heap区申请存储空间需要JAVA虚拟机(JVM)知识,可参考http://blog.csdn.net/mshootingstar/article/details/44783227和http://blog.csdn.net/wuwenxiang91322/article/details/10449177等,这方面内容后续整理。