String的intern()方法详解

官方API:

intern

public String intern()

返回字符串对象的规范化表示形式。

一个初始时为空的字符串池,它由类 String 私有地维护。

当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。

它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

所有字面值字符串和字符串赋值常量表达式都是内部的。

返回:

一个字符串,内容与此字符串相同,但它保证来自字符串池中。

------------------------------------------------------------------------------------------------

我们知道,一个Java程序运行后,String类会在内存的方法区中维护一个字符串池。对一个字符串调用intern()方法后,会先检查池内是否有该字符串,若有则返回;若没有没有则先创建再返回,确保返回的字符串已经以字面量的形式存在于池中。

上测试样例代码:

public class Test {
	public static void main(String argv[])
	{
		String s1 = "HelloWorld";
		String s2 = new String("HelloWorld");
		String s3 = "Hello";
		String s4 = "World";
		String s5 = "Hello" + "World";
		String s6 = s3 + s4;
		
		System.out.println(s1 == s2);
		System.out.println(s1 == s5);
		System.out.println(s1 == s6);
		System.out.println(s1 == s6.intern());
		System.out.println(s2 == s2.intern());
	}
}

测试结果:

false

true

false

true

false

解释一下:s1 创建的 HelloWorld 存在于方法区中的常量池其中的字符串池,而 s2 创建的 HelloWorld 存在于堆中,故第一条 false 。

s5 的创建是先进行右边表达式的字符串连接,然后才对 s5 进行赋值。赋值的时候会搜索池中是否有 HelloWorld 字符串,若有则把 s5 引用指向该字符串,若没有则在池中新增该字符串。显然 s5 的创建是用了 s1 已经创建好的字面量,故 true 。

第三个比较容易弄错,s6 = s3 + s4; 其实相当于 s6 = new String(s3 + s4); s6 是存放于堆中的,不是字面量。所以 s1 不等于 s6 。

第四个 s6.intern() 首先获取了 s6 的 HelloWorld 字符串,然后在字符串池中查找该字符串,找到了 s1 的 HelloWorld 并返回。这里如果事前字符串池中没有 HelloWorld 字符串,那么还是会在字符串池中创建一个 HelloWorld 字符串再返回。总之返回的不是堆中的 s6 那个字符串。

第四条也能解释为什么第五条是 false 。s2是堆中的 HelloWorld,s2.intern() 是字符串池中的 HelloWorld 。

--------------------------------------------------------------

在多一句,如果把

String s6 = s3 + s4;

 

改成

String s6 = (s3 + s4).intern();


会发生什么呢?

s6 存储的 HelloWorld 是存放字符串池中还是堆中呢?

答案是前者。

 


2019.03.15 为了回答 @yuanopen 的问题又搜了一些资料,做了一些测试,又有新收获,记录一下。

先说说常量池里的字符串怎么来。

JDK1.6及以前,调用String.intern(),如果常量池中没有,则拷贝一份对象,放到常量池中。

JDK1.7及以后,调用String.intern(),如果常量池中没有,则拷贝一份引用,放到常量池中。

这会导致intern()方法返回的字符串的含义有微小的区别,考虑常量池中现在没有字符串"test",现在有一个字符串s,内容为"test",JDK1.6以前,s.intern() 返回的地址是拷贝后的对象的地址,JDK1.7以后,返回的是s的地址,因此用 s == s.intern()这句判断JDK1.6为false,JDK1.7为true。

 

基于这个前提,回答JDK1.7情况下, @yuanopen的问题。

String s1 = new StringBuilder("ja").append("av").toString();

System.out.println(s1.intern() == s1); // true

String s2 = new StringBuilder("ja").append("va").toString();

System.out.println(s2.intern() == s2);  // false

第一句根据刚才的前提,好理解。

第二句,一开始我怀疑"java"这个字符串是默认存在于常量池中,所以s2.intern()返回了常量池中的"java",那自然就和new出来的"java"不等,于是进行了下列测试:

String s1 = new StringBuilder("java").toString();
System.out.println(s1.intern() == s1); //false

但这里犯了一个很基础的错误(即使是专门学习且记录了的我。。),上面已经说到,写在代码中的字符串会自动放入常量池。new StringBuilder("java");这一句就已经将"java"放到常量池,因此这个测试是错误的,通过编译后的class文件也能看出来。

修改测试代码,结果显示在注释中

String prefix = new String("j");
String suffix = new String("ava");
String s1 = prefix + suffix;
System.out.println(s1.intern() == s1); // false

String s2 = suffix + prefix;
System.out.println(s2.intern() == s2); //true

确认下字节码文件中没有出现java和avaj两个字符串:

因此推断"java"字符串确实存在于字符串常量池中,和StringBuilder、append没什么关系,因为最终toString()方法中还是通过new创建字符串。

搜索jdk源码,发现确实有声明"java"字符串的地方:

  • 16
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值