【译】Java中的字符串字面量
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
【译】Java中的字符串字⾯量
原⽂地址:
作者:Corey McGlone
让我们由⼀个简单的问题开始,什么是字符串字⾯量?⼀个字符串字⾯量就是两个双引号之间的字符序列,形如“string”、“literal”。
你可能已经在你的程序中使⽤字符串字⾯量⼏百次了,但是你可能还没意识到它在Java中是多么特殊。
字符串是不可变的
究竟什么使字符串字⾯量这么特殊?⾸先,记住重要的⼀点是字符串对象是不可变的。
这就意味着⼀旦创建,⼀个字符串对象就不能被改变(还是可以通过反射来改变)。
不可变?不能被更改?那怎么解释这段代码。
public class ImmutableStrings
{
public static void main(String[] args)
{
String start = "Hello";
String end = start.concat(" World!");
System.out.println(end);
}
}
// Output
Hello World!
看这段代码,字符串被改变了吗,还是没有?事实上,这段代码中并没有字符串对象被改变。
我们⾸先将“Hello”赋值给start变量,为了实现这步,需要在堆中创建⼀个对象,并把它的引⽤存储在start中。
接下来,我们在这个对象上调⽤concat(String)⽅法。
进⾏到这⾥Java耍了⼀个⼩把戏,如果我们查看,会发现其中对于concat(String)⽅法有如下的描述:
⽅法描述:将指定字符串连接在这个字符串的结尾。
如果长度为0,则返回这个字符串对象。
否则就创建⼀个新的字符串对象,表⽰这个字符串序列由原字符串对象和参数字符串⼆者所表⽰的字符串序列拼接⽽成。
你肯定看到了,当你将两个字符串做拼接操作时,实际上并没有改变原对象,⽽是直接创建了⼀个包含原始对象的新的对象,并且将另⼀个字符串拼在了后⾯。
我们上⾯那段代码就是这么执⾏的,start变量所引⽤的字符串对象并没有改变,如果在调⽤concat⽅法之后System.out.println(start); ,会发现start仍然指向的是“Hello”。
这时候你可能想到了字符串中的“+”操作符,事实上字符串的“+”操作也是和conca t做了同样的事情(“+”操作实际上是new了⼀个StringBuilder 对象,然后调⽤append⽅法)。
字符串的存储——字符串常量池
你或许听说过“字符串常量池”这个概念,究竟什么是字符串常量池?有⼈说是⼀个字符串对象容器。
答案很接近了,但是不完全正确。
事实上他是⼀⽤来保存字符串对象引⽤的容器。
即使字符串是不可变的,它仍然和Java中的其他对象⼀样。
对象都是创建在堆中,字符串也不例外。
所以字符串常量池仍然依靠堆,他们存储的只是堆中字符串的引⽤。
⽬前还没有解释这个池到底是什么,或者它为何存在。
好吧,因为字符串对象是不可变的,所以复制多个引⽤来“共享”这个字符串是安全的。
下⾯来看⼀个例⼦:
public class ImmutableStrings
{
public static void main(String[] args)
{
String one = "someString";
String two = "someString";
System.out.println(one.equals(two));
System.out.println(one == two);
}
}
// Output
true
true
在这个例⼦中,实在没有必要为⼀个相同的字符串对象创建两个实例。
如果字符串像StringBuffer⼀样是可变的,那么我们会被迫创建两个对象(如果不这样做的话,通过⼀个引⽤改变它的值,将会导致其他引⽤的值也同样改变,从⽽可能发⽣错误)。
但是,我们知道字符串对象是不能被改变的,我们可以安全地通过两个引⽤one和two来使⽤⼀个字符串对象。
这个⼯作是通过字符串常量池完成的,下⾯来看⼀下它是如何完成的:
当⼀个.java⽂件被编译成.class⽂件时,和所有其他常量⼀样,每个字符串字⾯量都通过⼀种特殊的⽅式被记录下来。
当⼀个.class⽂件被加载时(注意加载发⽣在初始化之前),JVM在.class⽂件中寻找字符串字⾯量。
当找到⼀个时,JVM会检查是否有相等的字符串在常量池中存放了堆中引⽤。
如果找不到,就会在堆中创建⼀个对象,然后将它的引⽤存放在池中的⼀个常量表中。
⼀旦⼀个字符串对象的引⽤在常量池中被创建,这个字符串在程序中的所有字⾯量引⽤都会被常量池中已经存在的那个引⽤代
替。
所以,在上⾯的例⼦中字符串常量池中只有⼀个引⽤,就是“someString”这个字符串对象的引⽤。
局部变量one和two都被赋予了同⼀个字符串对象的引⽤。
可以通过程序的输出来验证。
equals⽅法检查的是两个字符串对象是否包含相同的数据(“someString”),⽽“==”操作符作⽤在对象上⽐较的是引⽤是否相同,这意味着只有两个引⽤指向的是同⼀个对象才会返回true。
所以例⼦中的两个引⽤是相等的。
从输出可以看到,局部变量one和two不仅包含相同的数据,⽽且还指向相同的对象。
⽆图⽆真相,来看⼀下他们之间的关系:
注意,对于字符串字⾯量有⼀点⽐较特殊。
通过“new”关键字构建时⼀种不同的⽅式。
下⾯举⼀个例⼦:
public class ImmutableStrings
{
public static void main(String[] args)
{
String one = "someString";
String two = new String("someString");
System.out.println(one.equals(two));
System.out.println(one == two);
}
}
// Output
true
false
在这个例⼦中,可以看到由于关键字“new”,最后的结果有⼀点不同。
此例中,两个字符串字⾯量仍然被放进了常量池的常量表中,但是当使⽤“new”时,JVM就会在运⾏时创建⼀个新对象,⽽不是使⽤常量表中的引⽤。
虽然例⼦中的两个字符串引⽤所指向的对象包含相同的数据“someString”,但是这两个对象并不相同。
这⼀点可以从输出看出来,equals⽅法返回了true,⽽检查引⽤是否相等的“==”返回false。
这表明两个变量指向的是两个不同的字符串对象。
如果你想看图形化的表⽰,下⾯就是。
要记住引⽤到常量池的字符串对象是在类加载的时候创建的,⽽另⼀个对象是在运⾏时,当“new String”语句被执⾏时。
如果你想得到两个引⽤到相同对象的局部变量,你可以使⽤String类中的定义的intern()⽅法。
调⽤two.intern()后,会在字符串常量池中寻找是否有值相等的对象引⽤。
如果有的话,就会返回这个引⽤,然后你可以把它赋给局部变量。
如果这么做的化,局部变量one和two都是同⼀个对象的引⽤,并且在字符串常量池中也存有⼀个引⽤,就如同第⼀张图那样。
这时,在运⾏时创建的第⼆个字符串对象将会被GC回收。
垃圾回收
什么条件下对象才会被垃圾回收?当⼀个对象不再有引⽤指向它时,这个对象就会被回收。
有⼈注意到字符串字⾯量在垃圾回收时有什么特殊的地⽅吗?
让我们来看⼀个例⼦,然后你就会明⽩。
public class ImmutableStrings
{
public static void main(String[] args)
{
String one = "someString";
String two = new String("someString");
one = two = null;
}
}
在主函数结束前,有多少个对象可以被回收?0个?1个?还是2个?
答案是⼀个。
不像⼤多数对象,字符串字⾯量总是有⼀个来⾃字符串常量池的引⽤。
这就意味着它们会⼀直有⼀个引⽤,所以它们不会被垃圾回收。
见下图:
如你所见,局部变量one和two没有指向字符串对象,但是仍然有⼀个字符串常量池的引⽤。
所以GC并不会回收这个对象。
并且这个对象可以通过之前提到的intern()⽅法访问。
总结
对于字符串字⾯量,下⾯的⼏条结论你可以记住。
相等的字符串字⾯量将会指向相同的字符串对象(甚⾄是在不同包的不同类中)。
总之,字符串字⾯量不会被垃圾回收。
绝对不会。
在运⾏时创建的字符串和由字符串字⾯量创建的是两个不同的对象。
对于运⾏时创建的字符串你可以通过intern()⽅法来重⽤字符串字⾯量
使⽤equals()⽅法是⽐较两个字符串是否相等的最好⽅式。
下⾯的资料也⼀定要看:
关于字符串为什么被设计成不可变的可以参考:
字符串的创建和intern⽅法详解:
翻译如有错误,恳请纠正。
转载请注明原⽂链接:。