JavaSE_15th_常用类——String类intern()方法

一、从路径分隔符separator讲起

在java.lang.File类中有一个静态常量separator,表示表文件的路径分隔符。separator在windows下是反斜杠'\',在linux环境下是正斜杠'/',所以使用这个静态常量有助于java的跨平台性。
在java中,反斜杠'\'表示转义字符,'\'后面跟上'n'表示换行符,'\'后面跟上't'表示制表符,也就是说'\n'、'\t'这些功能符号不会被打印出'\n'、'\t'这些字符,
要打印出来只有加上转义字符'\',即'\n'=换行,'\t'=tab,而'\\n'=\n,'\\t'=\t,所以要打印转义字符本身的话,也需要写成'\\',就会打印出\
看下面的例子:

String separator = File.separator;
String dir1 = "mydir1" + separator + "mydir2";
System.out.println("dir1:" + dir1);
打印结果:

directory1:mydir1\mydir2

而如果不使用separator直接用'\',即String dir2 = "mydir1\mydir2";编译器就会提示错误,因为java认为'\'是转义字符,而'\'后面的内容并没有特殊意义。
于是让转义字符对自己进行转义,如下:

String dir2 = "mydir1\\mydir2";
System.out.println("dir2:" + dir2);
打印结果:
directory2:mydir1\mydir2
这在意料之中,说明转义字符(第1个'\')的确把后面的转义字符(第2个'\')转义成了普通字符'\'
现在我们观察到两次打印结果一模一样。
接下来的就有意思了,dir1和dir2是equals的吗?:
System.out.println("equals?:" + dir1.equals(dir2));
运行结果:
equals?:true
二者equals说明"mydir1" + separator + "mydir2"和"mydir1\\mydir2"的字符序列是一样的,于此推测separator的字符序列是"\\"

再来看看dir1和dir2是“==”的吗?:

System.out.println("==?:" + (dir1 == dir2));
运行结果:
==?:false
谨记一条原则:如果两个引用不"==",说明这两个引用指向不同的地址,
在深究之后,发现String dir1 = "mydir1" + separator + "mydir2";中的separator是个字符串引用变量,这使得连接好的字符串"mydir1\\mydir2"不会存放在常量池中,而是在堆中创建一个StringBuilder对象,将该字符序列"mydir1\\mydir2"存放在对象中,然后dir1指向该对象,也就是其地址是堆中的地址。
此时常量池中还没有"mydir1\\mydir2",当执行String dir2 = "mydir1\\mydir2";时,会去常量池中检查一下是否有"mydir1\\mydir2",结果发现没有,就会在常量池中存放一份"mydir1\\mydir2",也就是说dir2指向的是常量池中的一个地址。
在判断dir1和dir2是否"equals"时,会返回true,因为二者指向的字符串的字符序列完全一致。
在判断dir1和dir2是否"=="时,结果为false,因为dir1指向堆中,dir2指向常量池中。

有些人会疑惑,难道那个StringBuilder对象在存放"mydir1\\mydir2"时候,不会去检查一下常量池中是否有"mydir1\\mydir2"字符串吗,如果没有的话,不是应该先在常量池中
创建一份,然后再拷贝一份到这个对象中吗,也就是常量池中有一份,然后堆中的对象中有一份字符串的副本,对于String dir1 = new String("mydir1\\mydir2")来说,的确
是这样,请听我慢慢道来:

首先我们需要String类中一个很好用的方法intern(),当执行完String dir1 = new String("mydir1\\mydir2")这句代码后,常量池中会有一句"mydir1\\mydir2",堆中也会有一份该字符串的副本,这应该是我们达成一致的观点。紧接着,在这句代码的下面执行dir1.intern()这句代码,会产生什么效果?
这个方法会去常量池中检查是否有与dir1这个引用指向的对象中存放的字符序列一致的内容,结果发现常量池中已经有了"mydir1\\mydir2",然后它会很尴尬地返回该字符串序列(不好意思,对象先生,常量池中那旮旯已经有这货了,你看返回值就是证据)。
你可能会觉得这方法很多余,这不是当然的吗?堆中有的字符串,常量池能没有?
堆中有的的存货,常量池还真不一定有。
举个简单例子:
String s1 = new String("1") + new String("1");
new String("1")就是对象的引用值,我们可没有指定这值是常量,所以上面两个地址所指向的字符串对象做连接时,如之前说过的,Java会在堆中创建一个StringBuilder对象,将两个"1"连接成"11",然后放进该对象中。
现在有六个问题:
1)堆中有"1"吗?有!
2)哪儿来的?new String("1")的时候检查常量池中有没有"1",发现没有,然后常量池中生成一份"1",然后拷贝过来的。
3)常量池中有"1"吗?干嘛再问一遍?当然有啦,不然堆中的"1"哪儿来的。
4)堆中有"11"吗?有!
5)哪儿来的?Java会在堆中创建一个StringBuilder对象,将两个"1"连接成"11",然后放进该对象中。
6)常量池中有"11"吗?这个真没有,StringBuilder对象比较自私,只顾着给自己留一份,忘了通知常量池了。

你觉得不是这样?先看下面的例1:
package com.hpe.file;

public class Demo1 {
	public static void main(String[] args) {
		String s1 = new String("11");//常量池中有"11",堆中也有"11",s1指向堆中那份
		String s2 = "11";//s2指向常量池那份
		System.out.println(s1.equals(s2));//true
		System.out.println(s1 == s2);//false
	}
}
这个例子应该都能理解。

再看看下面这个例2:
package com.hpe.file;

public class Demo2 {
	public static void main(String[] args) {
		String s1 = new String("1") + new String("1");	//堆和常量池都有"1",而"11"只存在于堆,s1指向该"11"
		String s2 = "11";//常量池也有"11"了,s2指向常量池的"11"
		System.out.println(s1.equals(s2));//true
		System.out.println(s1 == s2);//false
	}
}
你依然不认可我的前两行注释,你认为在main执行完第一行代码的时候,"常量池中也是有"11"的",好吧,的确,这个例子证明不了我的观点,那再看看下面的例3:
package com.hpe.file;

public class Demo3 {
	public static void main(String[] args) {
		String s1 = new String("1") + new String("1");
		s1.intern();	//新加的一行代码
		String s2 = "11";
		System.out.println(s1.equals(s2));//true
		System.out.println(s1 == s2);//true
	}
}
我们新加了一行代码,看看打印结果,两个引用指向的竟然是同一个地址。
s1毋庸置疑指向的是堆中的"11"对象,这个打印结果说明s2也指向了堆中的"11"对象?是的。
按照你的理解:s2应该指向常量池中的"11"啊。

s1.intern()究竟有什么威力,你一向认为常量池是个地主,什么字符序列都有,堆中的字符串都是从常量池这里拷过去的副本,堆有的常量池都有,这是你的常识。
但其实堆空间藏了"私房钱",常量池还真有可能不知情。
在调用s1.intern();时,intern()去常量池中检查有没有和s1指向的堆中的"11"一样的字符序列,发现没找到,在JDK1.7之后,会在常量池中创建一个指向堆中"11"字符串的引用,
当执行String s2 = "11"时候,发现Java发现常量池找有没有"11",然后发现已经有一个指向堆中"11"的字符串引用,则把该引用的值拷贝给s2,那么s2也就指向了堆中的"11",
也就是说s2和s1还有常量池中那份"11"的引用同样都指向了堆中那份"11",所以s1 == s2的结果是true。

理解了这一点,那下面的程序也能理解了:

package com.hpe.file;

public class Demo4 {
	public static void main(String[] args) {
		String s1 = new String("1") + new String("1");
		String s2 = "11";
		s1.intern();
		System.out.println(s1.equals(s2));//true
		System.out.println(s1 == s2);//false
	}
}
总而言之,如果是通过StringBuilder对象存放的字符串,常量池中是没有的,如果该对象引用提前使用了intern()方法,就可以让常量池嬉皮笑脸地来讨要该字符串的引用,如果常量池在该对象调用intern()方法之前,就存放了相同内容的字符串,那intern()方法只能是尴尬地返回给对象:常量池已经自力更生创建了和你内容一样的字符串了。

在JDK1.7之前,intern()会把堆中有而常量池中没有的字符串对象直接拷贝到常量池中,也就是说常量池存的不是指向堆中"11"的字符串引用,而是直接把整个对象拷进常量池了,那么s2就会指向常量池中的"11"对象,因此在JDK1.7之前,Demo3的运行效果是这样的:

package com.hpe.file;

public class Demo3 {
	public static void main(String[] args) {
		String s1 = new String("1") + new String("1");
		s1.intern();	//新加的一行代码
		String s2 = "11";
		System.out.println(s1.equals(s2));//true
		System.out.println(s1 == s2);//false
	}
}
下一篇博文还会举几个关于intern()方法的小例子。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值