首页 Java java教程 深入分析Java中的intern()方法

深入分析Java中的intern()方法

May 19, 2017 am 09:47 AM
java

一、字符串问题

字符串在我们平时的编码工作中其实用的非常多,并且用起来也比较简单,所以很少有人对其做特别深入的研究。倒是面试或者笔试的时候,往往会涉及比较深入和难度大一点的问题。我在招聘的时候也偶尔会问应聘者相关的问题,倒不是说一定要回答的特别正确和深入,通常问这些问题的目的有两个,第一是考察对 JAVA 基础知识的了解程度,第二是考察应聘者对技术的态度。

我们看看以下程序会输出什么结果?如果你能正确的回答每一道题,并且清楚其原因,那本文对你就没什么太大的意义。如果回答不正确或者不是很清楚其原理,那就仔细看看以下的分析,本文应该能帮助你清楚的理解每段程序的结果及输出该结果的深层次原因。

代码段一:

package com.paddx.test.string;
public class StringTest {
    public static void main(String[] args) {
        String str1 = "string";
        String str2 = new String("string");
        String str3 = str2.intern();
 
        System.out.println(str1==str2);//#1
        System.out.println(str1==str3);//#2
    }
}
登录后复制

代码段二:

package com.paddx.test.string;
public class StringTest01 {
    public static void main(String[] args) {
        String baseStr = "baseStr";
        final String baseFinalStr = "baseStr";
 
        String str1 = "baseStr01";
        String str2 = "baseStr"+"01";
        String str3 = baseStr + "01";
        String str4 = baseFinalStr+"01";
        String str5 = new String("baseStr01").intern();
 
        System.out.println(str1 == str2);//#3
        System.out.println(str1 == str3);//#4
        System.out.println(str1 == str4);//#5
        System.out.println(str1 == str5);//#6
    }
}
登录后复制

代码段三(1):

package com.paddx.test.string;  
public class InternTest {
    public static void main(String[] args) {
 
        String str2 = new String("str")+new String("01");
        str2.intern();
        String str1 = "str01";
        System.out.println(str2==str1);//#7
    }
}
登录后复制

代码段三(2):

package com.paddx.test.string;
 
public class InternTest01 {
    public static void main(String[] args) {
        String str1 = "str01";
        String str2 = new String("str")+new String("01");
        str2.intern();
        System.out.println(str2 == str1);//#8
    }
}
登录后复制

为了方便描述,我对上述代码的输出结果由#1~#8进行了编码,下文中蓝色字体部分即为结果。

二、字符串深入分析

  1、代码段一分析

字符串不属于基本类型,但是可以像基本类型一样,直接通过字面量赋值,当然也可以通过new来生成一个字符串对象。不过通过字面量赋值的方式和new的方式生成字符串有本质的区别:

1.png

通过字面量赋值创建字符串时,会优先在常量池中查找是否已经存在相同的字符串,倘若已经存在,栈中的引用直接指向该字符串;倘若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。而通过new的方式创建字符串时,就直接在堆中生成一个字符串的对象(备注,JDK 7 以后,HotSpot 已将常量池从永久代转移到了堆中。详细信息可参考《JDK8内存模型-消失的PermGen》一文),栈中的引用指向该对象。对于堆中的字符串对象,可以通过 intern() 方法来将字符串添加的常量池中,并返回指向该常量的引用。

现在我们应该能很清楚代码段一的结果了:

结果 #1:因为str1指向的是字符串中的常量,str2是在堆中生成的对象,所以str1==str2返回false。

结果 #2:str2调用intern方法,会将str2中值(“string”)复制到常量池中,但是常量池中已经存在该字符串(即str1指向的字符串),所以直接返回该字符串的引用,因此str1==str2返回true。

以下运行代码段一的代码的结果:

1.png

 2、代码段二分析

对于代码段二的结果,还是通过反编译StringTest01.class文件比较容易理解:

常量池内容(部分):

1.png

执行指令(部分,第二列#+序数对应常量池中的项):

1.png

在解释上述执行过程之前,先了解两条指令:

ldc:Push item from run-time constant pool,从常量池中加载指定项的引用到栈。

astore_:Store reference into local variable,将引用赋值给第n个局部变量

现在我们开始解释代码段二的执行过程:

0: ldc           #2:加载常量池中的第二项(”baseStr”)到栈中。

2: astore_1      :将1中的引用赋值给第一个局部变量,即String baseStr = “baseStr”;

3: ldc           #2:加载常量池中的第二项(”baseStr”)到栈中。

5: astore_2      :将3中的引用赋值给第二个局部变量,即 final String baseFinalStr=”baseStr”;

6: ldc           #3:加载常量池中的第三项(”baseStr01″)到栈中。

8: astore_3     :将6中的引用赋值给第三个局部变量,即String str1=”baseStr01″;

9: ldc           #3:加载常量池中的第三项(”baseStr01″)到栈中。

11: astore        4:将9中的引用赋值给第四个局部变量:即String str2=”baseStr01″;

结果#3:str1==str2 肯定会返回true,因为str1和str2都指向常量池中的同一引用地址。所以其实在JAVA 1.6之后,常量字符串的“+”操作,编译阶段直接会合成为一个字符串。

13: new           #4:生成StringBuilder的实例。

16: dup        :复制13生成对象的引用并压入栈中。

17: invokespecial #5:调用常量池中的第五项,即StringBuilder.方法。

以上三条指令的作用是生成一个StringBuilder的对象。

20: aload_1  :加载第一个参数的值,即”baseStr”

21: invokevirtual #6 :调用StringBuilder对象的append方法。

24: ldc           #7:加载常量池中的第七项(”01″)到栈中。

26: invokevirtual #6:调用StringBuilder.append方法。

29: invokevirtual #8:调用StringBuilder.toString方法。

32: astore        5:将29中的结果引用赋值改第五个局部变量,即对变量str3的赋值。

结果 #4:因为str3实际上是stringBuilder.append()生成的结果,所以与str1不相等,结果返回false。

34: ldc           #3:加载常量池中的第三项(”baseStr01″)到栈中。

36: astore        6:将34中的引用赋值给第六个局部变量,即str4=”baseStr01″;

结果 #5 :因为str1和str4指向的都是常量池中的第三项,所以str1==str4返回true。这里我们还能发现一个现象,对于final字段,编译期直接进行了常量替换,而对于非final字段则是在运行期进行赋值处理的。

38: new           #9:创建String对象

41: dup               :复制引用并压如栈中。

42: ldc           #3:加载常量池中的第三项(”baseStr01″)到栈中。

44: invokespecial #10:调用String.””方法,并传42步骤中的引用作为参数传入该方法。

47: invokevirtual #11:调用String.intern方法。

从38到41的对应的源码就是new String(“baseStr01″).intern()。

50: astore        7:将47步返回的结果赋值给变量7,即str5指向baseStr01在常量池中的位置。

结果 #6 :因为str5和str1都指向的都是常量池中的同一个字符串,所以str1==str5返回true。

运行代码段二,输出结果如下:

1.png

3、代码段三解析:

对于代码段三,在 JDK 1.6 和 JDK 1.7中的运行结果不同。我们先看一下运行结果,然后再来解释其原因:

JDK 1.6 下的运行结果:

1.png

JDK 1.7 下的运行结果:

1.png

根据对代码段一的分析,应该可以很简单得出 JDK 1.6 的结果,因为 str2 和 str1本来就是指向不同的位置,理应返回false。

比较奇怪的问题在于JDK 1.7后,对于第一种情况返回true,但是调换了一下位置返回的结果就变成了false。这个原因主要是从JDK 1.7后,HotSpot 将常量池从永久代移到了元空间,正因为如此,JDK 1.7 后的intern方法在实现上发生了比较大的改变,JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。所以:

结果 #7:在第一种情况下,因为常量池中没有“str01”这个字符串,所以会在常量池中生成一个对堆中的“str01”的引用,而在进行字面量赋值的时候,常量池中已经存在,所以直接返回该引用即可,因此str1和str2都指向堆中的字符串,返回true。

结果 #8:调换位置以后,因为在进行字面量赋值(String str1 = “str01″)的时候,常量池中不存在,所以str1指向的常量池中的位置,而str2指向的是堆中的对象,再进行intern方法时,对str1和str2已经没有影响了,所以返回false。

【相关推荐】

1. Java免费视频教程

2. JAVA中intern()方法的使用经验小结

3. java中intern方法的概念是什么

4. 分析Java中的intern()的作用

5. 详解String对象中的 intern()

以上是深入分析Java中的intern()方法的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Java 中的完美数 Java 中的完美数 Aug 30, 2024 pm 04:28 PM

Java 完美数指南。这里我们讨论定义,如何在 Java 中检查完美数?,示例和代码实现。

Java中的Weka Java中的Weka Aug 30, 2024 pm 04:28 PM

Java 版 Weka 指南。这里我们通过示例讨论简介、如何使用weka java、平台类型和优点。

Java 中的史密斯数 Java 中的史密斯数 Aug 30, 2024 pm 04:28 PM

Java 史密斯数指南。这里我们讨论定义,如何在Java中检查史密斯号?带有代码实现的示例。

Java Spring 面试题 Java Spring 面试题 Aug 30, 2024 pm 04:29 PM

在本文中,我们保留了最常被问到的 Java Spring 面试问题及其详细答案。这样你就可以顺利通过面试。

突破或从Java 8流返回? 突破或从Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一种强大且表达力丰富的处理数据集合的方式。然而,使用Stream时,一个常见问题是:如何从forEach操作中中断或返回? 传统循环允许提前中断或返回,但Stream的forEach方法并不直接支持这种方式。本文将解释原因,并探讨在Stream处理系统中实现提前终止的替代方法。 延伸阅读: Java Stream API改进 理解Stream forEach forEach方法是一个终端操作,它对Stream中的每个元素执行一个操作。它的设计意图是处

Java 中的时间戳至今 Java 中的时间戳至今 Aug 30, 2024 pm 04:28 PM

Java 中的时间戳到日期指南。这里我们还结合示例讨论了介绍以及如何在java中将时间戳转换为日期。

Java程序查找胶囊的体积 Java程序查找胶囊的体积 Feb 07, 2025 am 11:37 AM

胶囊是一种三维几何图形,由一个圆柱体和两端各一个半球体组成。胶囊的体积可以通过将圆柱体的体积和两端半球体的体积相加来计算。本教程将讨论如何使用不同的方法在Java中计算给定胶囊的体积。 胶囊体积公式 胶囊体积的公式如下: 胶囊体积 = 圆柱体体积 两个半球体体积 其中, r: 半球体的半径。 h: 圆柱体的高度(不包括半球体)。 例子 1 输入 半径 = 5 单位 高度 = 10 单位 输出 体积 = 1570.8 立方单位 解释 使用公式计算体积: 体积 = π × r2 × h (4

如何在Spring Tool Suite中运行第一个春季启动应用程序? 如何在Spring Tool Suite中运行第一个春季启动应用程序? Feb 07, 2025 pm 12:11 PM

Spring Boot简化了可靠,可扩展和生产就绪的Java应用的创建,从而彻底改变了Java开发。 它的“惯例惯例”方法(春季生态系统固有的惯例),最小化手动设置

See all articles