字符串是不可变的
究竟什么使字符串字面量这么特殊?首先,记住重要的一点是字符串对象是不可变的。
这就意味着一旦创建,一个字符串对象就不能被改变(还是可以通过反射来改变)。
不可变?不能被更改?那怎么解释这段代码。
public class ImmutableStrings{ public static void main(String[] args) { String start = "Hello"; String end = start.concat(" World!"); System.out.println(end); }}// OutputHello World!
看这段代码,字符串被改变了吗,还是没有?事实上,这段代码中并没有字符串对象被改变。
我们首先将“Hello”赋值给start变量,为了实现这步,需要在堆中创建一个对象,并把它的引用存储在start中。接下来,我们在这个对象上调用concat(String)方法。进行到这里Java耍了一个小把戏,如果我们查看String的API说明,会发现其中对于concat(String)方法有如下的描述:
方法描述:将指定字符串连接在这个字符串的结尾。
如果长度为0,则返回这个字符串对象。否则就创建一个新的字符串对象,表示这个字符串序列由原字符串对象和参数字符串二者所表示的字符串序列拼接而成。
你肯定看到了,当你将两个字符串做拼接操作时,实际上并没有改变原对象,而是直接创建了一个包含原始对象的新的对象,并且将另一个字符串拼在了后面。
我们上面那段代码就是这么执行的,start变量所引用的字符串对象并没有改变,如果在调用concat方法之后System.out.println(start);,会发现start仍然指向的是“Hello”。
这时候你可能想到了字符串中的“+”操作符,事实上字符串的“+”操作也是和concat做了同样的事情(“+”操作实际上是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); }}// Outputtruetrue
在这个例子中,实在没有必要为一个相同的字符串对象创建两个实例。如果字符串像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); }}// Outputtruefalse
在这个例子中,可以看到由于关键字“new”,最后的结果有一点不同。
此例中,两个字符串字面量仍然被放进了常量池的常量表中,但是当使用“new”时,JVM就会在运行时创建一个新对象,而不是使用常量表中的引用。
虽然例子中的两个字符串引用所指向的对象包含相同的数据“someString”,但是这两个对象并不相同。
这一点可以从输出看出来,equals方法返回了true,而检查引用是否相等的“==”返回false。
这表明两个变量指向的是两个不同的字符串对象。
如果你想看图形化的表示,下面就是。要记住引用到常量池的字符串对象是在类加载的时候创建的,而另一个对象是在运行时,当“new String”语句被执行时。
如果你想得到两个引用到相同对象的局部变量,你可以使用String类中的定义的intern()方法。
调用two.intern()后,会在字符串常量池中寻找是否有值相等的对象引用。
如果有的话,就会返回这个引用,然后你可以把它赋给局部变量。
如果这么做的化,局部变量one和two都是同一个对象的引用,并且在字符串常量池中也存有一个引用,就如同第一张图那样。这时,在运行时创建的第二个字符串对象将会被GC回收。
以上就是深圳达内教育java培训机构的小编针对“Java编程技术分享之字符串字面量”的内容进行的回答,希望对大家有所帮助,如有疑问,请在线咨询,有专业老师随时为你服务。