如何完美解答面试问题——一文搞懂Java中的intern()函数

 大家好,我是孤焰。今天这篇文章仍然算在面试问题解答的系列中,但是要谈一道笔试难题——Java中intern()函数
 许多小伙伴都不太了解intern()函数,所以今天这篇文章让你认识intern(),助你早日乘上大厂直通车。

1.intern()函数是做什么的?

intern()函数:intern()函数是String对象的一个成员方法,调用intern()函数会返回该字符串在字符串常量池(StringTable)中的地址引用。

2.intern()详细介绍

intern()函数的功能s.intern()调用时(s为String类型,例如String s = new String(“1”)),intern()函数会判断字符串常量池(StringTable)中是否含有s对象这个字符串,如果在常量池中存在"1"这个字符串,那么返回"1"在常量池中的引用。如果常量池中不存在"1"这个字符串,那么会在常量池中创建一个"1",然后返回"1"在常量池中的引用。

 具体学习intern()函数下面将以举例的形式进行讲解:

 大家先看一下下面的示例,你认为程序会输出什么呢?又分别向字符串常量池中添加了多少个常量呢?(答案写在了程序注释上面)

//示例1
public class Main {
    public static void main(String[] args) {
        String s = new String("1");		//此时常量池中有1个常量
        s.intern();						//此时常量池中有1个常量
        String s2 = "1";				//此时常量池中有1个常量
        System.out.println(s == s2);	//JDK1.6:false	JDK7及以后:false
    }
}

 示例1中,在创建s对象的时候因为显示的使用"1"字符串常量创建,所以会向字符串常量池中添加"1"这个常量。注:会向字符串常量池中添加"1"这个常量,是因为使用"1"这个常量才向池中添加的,而不是因为创建s对象才向池中添加的

 当s对象调用intern()函数时,因为池中已经含有"1"常量,所以将返回池中常量的引用,但是因为在代码中并没有将返回的引用赋值给对象,所以intern()调用在示例1中并没有实际用处

 由于s变量指向的引用并没有指向池中常量的引用,所以在判断s==s2的时候两个对象的引用并不相等,故输出false,这个不管在JDK1.6还是JDK7及以后的版本结果都是一样的。

//示例2
public class Main {
    public static void main(String[] args) {
		String s3 = new String("2");		//此时常量池中有1个常量
        s3 = s3.intern();					//此时常量池中有1个常量
        String s4 = "2";					//此时常量池中有1个常量
        System.out.println(s3 == s4);	//JDK1.6:true	JDK7及以后:true
    }
}

 示例2中的思路和示例1中类似,为节省篇幅这里不再复述一遍。示例2和示例1的区别在于s3=s3.intern();,示例2中s3对象调用intern()函数并将返回的引用赋值给了s3对象,所以这里s3对象指向字符串常量池中"2"的引用。然后在判断s3==s4的时候两个对象的引用相等,故输出true,这个不管在JDK1.6还是JDK7及以后的版本结果都是一样的。

//示例3
public class Main {
    public static void main(String[] args) {
		String s5 = new String("3") + new String("3");		//此时常量池中有1个常量
		s5.intern();										//此时常量池中有2个常量
		String s6 = "33";									//此时常量池中有2个常量
		System.out.println(s5 == s6);	//JDK1.6:false	JDK7及以后:true
    }
}

 示例3中,注:执行完第4行代码时,字符串常量池中仅有1个常量。有的小伙伴可能会认为执行完第4行代码后常量池中会有两个常量分别为"3"和"33"。但是其实不然,常量池中仅仅只有"3"这个常量。大家可能知道字符串在进行拼接的时候实际上是创建了一个StringBuilder对象然后调用append()函数分别将字符串添加进去,最后调用toString()StringBuilder转换为String对象。

 通过jclasslib字节码分析工具可以发现执行完第4行代码后,字符串常量池中仅仅只有"3"这个常量,字节码分析图如下图:
在这里插入图片描述

 图中ldc命令即可理解为向字符串常量池中添加常量,而添加常量的内容可以看ldc命令后<>里面的内容。可以发现在12行命令为调用intern()函数,在这行命令之前仅仅调用过两次ldc命令,且全部都是向常量池中添加"3"这个常量,这就说明String s5 = new String(“3”) + new String(“3”);这行代码仅仅只向常量池中添加了"3"这个常量,并没有向常量池中添加"33"这个常量。

 解释清楚了字符串拼接并不会向字符串常量池中添加新的字符串常量这个知识后,有的读者比较细心可能发现了这个示例3在JDK1.6中输出的结果与在JDK7及以后中输出的结果不同。下面就为你揭开intern()的神秘面纱。

 熟悉java虚拟机JVM的读者可能知道,在JDK1.6过渡到JDK1.7之后java虚拟机的运行时数据区发生了改变,原本放在方法区中的字符串常量池(StringTable)被转移到了java堆中。

 下面以图解的形式,解释intern()函数在JVM层面的运行过程:
在这里插入图片描述
 图1为示例3中执行完第4行代码的运行时数据区的状态(JDK1.6版本的状态),此时s5对象指向堆中的实例引用,字符串常量池中并没有"33"常量。

在这里插入图片描述

 图2为调用了intern()函数之后运行时数据区的状态,此时s5对象仍指向实例数据的引用,所以在判断s5==s6的时候返回false。注:图1和图2是在JDK1.6中的情况

 JDK1.6中调用了intern()函数后会在字符串常量池中创建一个"33"常量并令s5对象指向"33"常量。

在这里插入图片描述

 图3为示例3中执行完第4行代码的运行时数据区的状态(JDK1.7及以后版本的状态),此时s5对象指向堆中的实例引用,字符串常量池中并没有"33"常量。

在这里插入图片描述

 图4为调用了intern()函数之后运行时数据区的状态,此时s5对象指向实例数据的引用,s6指向"33","33"又指向实例数据的引用,所以在判断s5==s6的时候返回true。注:图3和图4是在JDK1.7及以后版本的情况

 JDK1.7及以后版本中调用了intern()函数后会在字符串常量池中创建一个"33"变量并令"33"变量指向s5实例数据的引用。

//示例4
public class Main {
    public static void main(String[] args) {
		String s7 = new String("4") + new String("4");		//此时常量池中有1个常量
		String s8 = "44";									//此时常量池中有2个常量
		s7.intern();										//此时常量池中有2个常量
		System.out.println(s7 == s8);	//JDK1.6:false	JDK7及以后:false
    }
}

 通过上述例子就能明白示例4为什么JDK1.6和JDK1.7以后的版本都是输出false的结果了。

 执行完第4行代码后,常量池中有1个常量。执行完第5行代码后,常量池中有两个常量。然后在第6行代码调用intern()函数,这个时候因为常量池中已经有"44"这个常量了,所以不管哪个版本中都是返回常量池中已有的常量引用。注:两个版本的区别在于常量池中没有某个字符串,然后通过调用intern()函数向字符串常量池中添加常量,这样再去比较两个变量的引用可能会产生差别

//示例5
public class Main {
    public static void main(String[] args) {
		String s9 = new String("5") + new String("5");		//此时常量池中有1个常量
		String s10 = "55";									//此时常量池中有2个常量
		s9 = s9.intern();									//此时常量池中有2个常量
		System.out.println(s9 == s10);	//JDK1.6:true	JDK7及以后:true
    }
}

 示例5也同示例4是一样的道理,将常量池中的常量引用赋值给了s9,又因为s10指向常量池中的常量引用,所以在进行s9==s10比较的时候会返回true,最后输出true的结果。

3.总结String的intern()的使用:

 JDK1.6中,将这个字符串对象尝试放入串池。

  • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
  • 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址

 JDK1.7起,将这个字符串对象尝试放入串池。

  • 如果串池中有,则并不会放入。返回已有的串池中对象的地址
  • 如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中的引用地址

4.最后

都看到最后了,求求大家点个赞再走吧!你的支持是博主创作的最大动力。

在这里插入图片描述

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值