Java中String.intern()的理解

本文围绕Java运行时常量池展开,介绍其存放的字面量和符号引用两类常量,阐述了在不同JDK版本中运行时常量池的位置变化。详细分析了String对象放入常量池的时机,通过多段代码示例进行说明,还讲解了String.intern()方法及其在JDK1.7的改变。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

运行时常量池概述

Java运行时常量池中主要存放两大类常量:字面量和符号引用。字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。
而符号引用则属于编译原理方面的概念,包括了下面三类常量:
- 类和接口的全限定名(包名+类名)
- 字段的名称和描述符
- 方法的名称和描述符

 

运行时常量池位置

运行时常量池在JDK1.6及之前版本的JVM中是方法区的一部分,而在HotSpot虚拟机中方法区放在了”永久代(Permanent Generation)”。所以运行时常量池也是在永久代的。
但是JDK1.7及之后版本的JVM已经将运行时常量池从方法区中移了出来,在Java 堆(Heap)中开辟了一块区域存放运行时常量池

 

String何时放入常量池

记住一句话:直接使用双引号声明出来的String对象会直接存储在常量池中。

 

代码一:

String a = "计算机软件";

分析:因为计算机软件五个字直接使用了双引号声明,故JVM会在运行时常量池中首先查找有没有该字符串,有则直接返回该字符串在常量池中的引用;没有则直接在常量池中创建该字符串,然后返回引用。此时,该句代码已经执行完毕,不会在java Heap(堆)中创建内容相同的字符串。该字符串只在常量池中创建了一个String对象。

 

代码二:

String a = new String("计算机软件");

分析:该行代码生成了两个String对象(Stack(栈)中的对象引用不在讨论范围内):第一步,因为计算机软件五个字直接使用了双引号声明,故JVM会在运行时常量池中首先查找有没有该字符串,有则进入第二步;没有则直接在常量池中创建该字符串,然后进入第二步。第二步:在常量池中创建了一个String对象之后,由于使用了new,JVM会在Heap(堆)中创建一个内容相同的String对象,然后返回堆中String对象的引用该行代码分别在常量池和堆中生成了两个内容相同的String对象。

 

代码三:

String a = "计算机" + "软件";

分析:由于JVM存在编译期优化,对于两个直接双引号声明的String的+操作,JVM在编译期会直接优化为“计算机软件”一个字符串,故该行代码同代码一

 

代码四:

String b = "计算机";
String a = b + "软件";

分析:由于b是一个String变量,编译期无法确定b的值,故不会优化为一个字符串。即使我们知道b的值,但JVM认为它是个变量,变量的值只能在运行期才能确定,故不会优化。运行期字符串的+连接符相当于new,故该行代码在Heap中创建了一个内容为“计算机软件”的String对象,并返回该对象的引用。至此,该代码执行完毕,因为没有直接双引号声明计算机软件这5个字的字符串,故常量池中不会生成“计算机软件“”这5个字的字符串。但是会有“计算机”和“软件”这两个String对象,因为他们都用双引号声明了。

 

代码五:

String final b = "计算机";
String a = b + "软件";

分析:该代码与代码四的唯一区别是将b声明为final类型,即为常量。故在编译期JVM能确定b的值,所以对+可以优化为“计算机软件”5个字的字符串。该代码的运行同代码三和代码一

 

代码六:

String a = new String("计算机") + "软件";

分析:因为有new,该代码也无法编译期优化,故该行代码只是在Heap中生成了“计算机软件”字符串的String对象,在常量池中没有内容相同的对象生成。

 

代码七:

String a = new String("计算机") + new String("软件");

分析:因为有new,该代码也无法编译期优化,该行代码在常量池中生成了“计算机”,“软件”两个字符串,仅在Heap中生成了“计算机软件”字符串的String对象,而在常量池中没有“计算机软件”字符串对象的生成。

 

String.intern()方法

概述

String.intern()是一个Native方法,它的作用是:如果运行时常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此String内容相同的字符串,并返回常量池中创建的字符串的引用。

JDK1.7改变

当常量池中没有该字符串时,JDK7的intern()方法的实现不再是在常量池中创建与此String内容相同的字符串,而改为在常量池中记录Java Heap中首次出现的该字符串的引用,并返回该引用

String s1 = new String("测试") + new String("代码");
String s2 = "测试代码";
String s3 = s1.intern();
System.out.println(s1==s2);  //jdk1.7 false
System.out.println(s3==s2);  //jdk1.7 true

分析:第一行代码仅在Heap区生成了“测试代码”对象,该对象由s1指向;而第二行代码在字符串常量池中生成了“测试代码”对象,该对象由s2指向;由于在字符串常量区已经存在了“测试代码”对象,故第三行的intern()方法返回的是指向字符串常量区的引用,而s1依旧指向Heap区的对象。

String s1 = new String("测试") + new String("代码");
String s3 = s1.intern();
String s2 = "测试代码";
System.out.println(s1==s2);  //jdk1.7 true  不可思议
System.out.println(s3==s2);  //jdk1.7 true

分析:第一行代码仅在Heap区生成了“测试代码”对象,该对象由s1指向;注意第二行代码,在使用intern()方法时,由于此时在字符串常量区不存在“测试代码”字符串,故intern()方法实际上并没有再存储一份“测试代码”的对象,而是在字符串常量区直接存储了一个引用s3,这份引用指向s1引用的对象,即指向Heap区中“测试代码”对象;最后,第三行String s2="测试代码"是显示声明的,因此会直接去常量池中创建,而在创建时会发现已经有了这个对象的(通过存储在常量池中的指向Heap区对象的引用s3发现的)所以s2这个引用就指向s3引用指向的对象,即此时s1,s2,s3均指向Heap区的“测试代码”对象。

 

String放入运行时常量池的时机与String.intern方法解惑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值