彻底搞懂String的intern方法

概述

在介绍intern方法之前,先思考一个问题,下面这句代码会产生几个对象?

new String("abc");

答案是可能会产生一个、也可能会产生两个对象。

要回答这个问题,我们需要了解一下Java关于String类的工作原理:Java为了避免产生大量的字符串,设计了一个字符串常量池,在创建一个String对象的时候,JVM首先会检查字符串常量池中是否有值相等的字符串,如果有,则不再创建,直接返回该字符串的引用地址,然后在堆中创建一个String对象,将该引用地址存储进堆中的String对象里;若没有,则先在字符串常量池创建一个字符串,返回该字符串引用地址,然后在堆中创建一个String对象,将该引用地址保存进堆里的String对象中。

因此,new一个String对象的时候,如果字符串常量池中存在该字符串,则只需要在堆里创建一个String对象,如果字符串常量池里不存在该字符串,则需要创建两个。

Demo

public class Demo3 {


    public static void main(String[] args) {


        String str1 = "hello";
        String str2 = "hello";
        String str3 = new String("hello");
        System.out.println("str1和str2的引用是否相同: " + (str1==str2));
        System.out.println("str1和str2的值是否相同: " + str1.equals(str2));
        System.out.println("str1和str3的引用是否相同: " + (str1==str3));
        System.out.println("str1和str3的值是否相同: " + str1.equals(str3));
    }


}
str1和str2的引用是否相同: true
str1和str2的值是否相同: true
str1和str3的引用是否相同: false
str1和str3的值是否相同: true

Process finished with exit code 0

 首先我们了解一下String str = "hello" 和new一个String对象的区别:new一个String对象的过程上面已经介绍过了,而直接将字面量赋值给String,它的运行过程是它会去字符串常量池中寻找是否存在该字符串,如果存在,则返回引用,如果不存在,则创建一个字符串常量然后返回引用,可以看出,直接字面量赋值和new一个String对象的区别仅仅是堆里是否创建String对象。

然后我们分析上面的Demo,首先会在字符串常量池中创建一个"hello"字符串,然后将引用赋值给str1,执行第str2时,由于字符串常量池中已经存在"hello"字符串,因此不再创建,将之前创建的字符串引用赋值给str2,因此str2和str1保存同一个引用,都指向字符串常量池中"hello"字符串,执行str3时,发现字符串常量池里有"hello"字符串,然后拿到该引用,在堆中创建一个String对象,将引用保存进String对象中,然后将String对象的引用返回给str3,因此str1和str3的引用不相等,值相等。

intern方法

有了上面的基础之后,我们就可以开始研究String的intern方法,它的作用如下:

JDK1.6:首先判断字符串常量池中是否存在String对象的字符串,如果存在,则返回该引用,如果不存在,则从String对象中将字符串拷贝一份放到常量池,然后返回该引用。此时,字符串常量池是在方法区里的。

JDK1.7:首先判断字符串常量池中是否存在String对象的字符串,如果存在,则返回该引用,如果不存在,则将String对象的引用放到常量池中,然后返回该引用。此时,由于常量池在方法区中难以GC回收,因此将常量池放到堆里。

JDK1.8:intern的作用与JDK1.7保持一致,但是常量池的位置发生变化,将常量池从堆中放到了元空间,去除了方法区。

搞清楚intern方法的作用后,来看下面的Demo

public class Demo9 {

    public static void main(String[] args) {

        String str1 = new String("he") + new String("llo");
        str1.intern();
        String str2 = "hello";
        System.out.println(str2 == str1);
    }

}

执行第一句代码,在常量池中创建"he"和"llo"两个字符串,在堆中创建"he","llo","hello"三个对象

执行第二句代码,由于常量池中不存在"hello",此时,JDK版本不同,处理不同,JDK1.6时,它会将堆里的String对象字符串拷贝一份放到常量池里,然后返回引用,JDK1.7时,它会将堆里的String对象引用放到常量池里,然后返回引用

执行第三句代码,由于常量池里已经存在"hello",因此不再创建,直接返回该引用

执行第四句代码,如果是JDK1.6,则str1指向堆里的String对象,str2指向常量池里的"hello"字符串,结果false

                            如果是JDK1.7,则str1和str2都指向堆里的String对象,结果true

public class Demo10 {


    public static void main(String[] args) {

        String str1 = new String("he") + new String("llo");
        String str2 = "hello";
        String str3 = str1.intern();
        System.out.println(str2 == str1);
        System.out.println(str2 == str3);
    }
}

执行第一句代码,在常量池中创建"he"和"llo"两个字符串,在堆中创建"he","llo","hello"三个对象

执行第二句代码,在常量池中创建"hello"字符串,返回该引用

执行第三句代码,由于常量池里已经存在"hello",因此不再创建,直接返回该引用

执行第四句代码,str2和str3指向的都是常量池的字符串,str1指向堆里的String对象,因此结果为false和true

public class Demo6 {


    public static void main(String[] args) {

        String str1 = "he" + "llo";
        String str2 = str1.intern();
        System.out.println(str1 == str2);
    }

}

刚才我们测试两个new String对象相加,现在我们来测试两个常量相加结果会如何?

对于两个常量相加,编译器会在编译阶段,将两个常量合并,因此,上面的第一句等同于String str1 = "hello"

因此,上面的代码首先会在常量池里创建一个"hello"字符串,然后执行str2,由于常量池存在"hello"字符串,则将该引用返回给

str2,因此结果为true

public class Demo11 {


    public static void main(String[] args) {

        String str1 = "hello";
        String str2 = "he";
        String str3 = "llo";
        String str4 = str2 + str3;
        System.out.println(str1 == str4);
    }
}

现在来测试两个变量相加,结果会如何?

首先在常量池创建"hello","he","llo"三个字符串,由于两个变量相加,是在运行期利用StringBuffer来拼接的,因此str4指向的是堆中的"hello"对象,而该对象保存了常量池中的"hello"字符串引用,也就是str1,因此结果为false

public class Demo11 {


    public static void main(String[] args) {

        String str1 = "hello";
        final String str2 = "he";
        final String str3 = "llo";
        String str4 = str2 + str3;
        System.out.println(str1 == str4);
    }
}

将两个变量修饰为final类型,则在编译期就会初始化值,相当于两个常量,两个常量相加在之前举过例子,因此不再赘述,结果为true,str1和str4都指向常量池中的字符串

常见面试题

Q:下列程序的输出结果: 
String s1 = “abc”; 
String s2 = “abc”; 
System.out.println(s1 == s2); 
A:true,均指向常量池中对象。

Q:下列程序的输出结果: 
String s1 = new String(“abc”); 
String s2 = new String(“abc”); 
System.out.println(s1 == s2); 
A:false,两个引用指向堆中的不同对象。

Q:下列程序的输出结果: 
String s1 = “abc”; 
String s2 = “a”; 
String s3 = “bc”; 
String s4 = s2 + s3; 
System.out.println(s1 == s4); 
A:false,因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。

Q:下列程序的输出结果: 
String s1 = “abc”; 
final String s2 = “a”; 
final String s3 = “bc”; 
String s4 = s2 + s3; 
System.out.println(s1 == s4); 
A:true,因为final变量在编译后会直接替换成对应的值,所以实际上等于s4=”a”+”bc”,而这种情况下,编译器会直接合并为s4=”abc”,所以最终s1==s4。

Q:下列程序的输出结果: 
String s = new String(“abc”); 
String s1 = “abc”; 
String s2 = new String(“abc”); 
System.out.println(s == s1.intern()); 
System.out.println(s == s2.intern()); 
System.out.println(s1 == s2.intern()); 
A:false,false,true。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值