String字符串进入常量池的时机以及intern()方法

String对象的创建过程

先举一个例子 String str="java";

过程分析:在class文件被JVM装载到内存中,JVM会创建一块 String Pool(字符串常量池)。当执行到String str="java"时候,JVM会首先在string pool 中查看是否已经存在字符串对象“java”,如果已经存在,则不用创建新的对象,直接将str指向string pool中已经存在的对象“java”,如果不存在,则先在string pool中创建一个新的字符串“java”,然后将str指向它。

String str=new String("java");

过程分析:当执行String str=new String("java")时候,jvm会首先在string pool中查看是否已经存在字符串对象“java”,如果已经存在,则不用再string pool中创建新的对象,直接执行new String("java")构造方法,在堆里面创建一个字符串“java”,然后将引用str指向它。如果在String pool中不存在该对象,则先在String pool中创建一个新的字符串“java”,然后再进行new String("java")等操作。

然后再来看下面的例子:

public class TestString{    
     public static void main(String args[]){    
         String s1 = new String("abc");//语句1    
         String s2 = "abc";//语句2    
         String s3 = new String("abc");//语句3    
    
         System.out.println(s1 == s2);//语句4    
         System.out.println(s1 == s3);//语句5    
         System.out.println(s2 == s3);//语句6    
     
         System.out.println(s1 == s1.intern());//语句7    
         System.out.println(s2 == s2.intern());//语句8    
         System.out.println(s1.intern() == s2.intern());//语句9    
     
         String hello = "hello";//语句10    
         String hel = "hel";//语句11    
         String lo = "lo";//语句12    
     
         System.out.println(hello == "hello");//语句13    
         System.out.println(hello == "hel" + "lo");//语句14    
         System.out.println(hello == "hel" + lo);//语句15    
         System.out.println(hello == hel + lo);//语句16    
     }    
 }   

问题1:当执行完语句(1)时,在内存里面生成几个对象?它们是什么?在什么地方? 

答案:内存生成了2个对象,他们的内容分别都是abc,分别在String Pool (常量池)和Heap(堆)里面。

分析:首先在string pool里面查找是否有“abc”,如果有就直接使用,但是这是本程序的第一条语句,显然之前不存在一个对象“abc”,所以要在string pool中生成一个对象“abc”,接下来执行new String("abc")构造方法,new 出来的对象都放在Heap里面,在Heap里面又创建了一个“abc”的对象。这个时候内存就有2个对象了,一个在String pool里面,一个在Heap里面。

问题2:当执行完语句(2)时,在内存里面一共有几个对象?它们是什么?在什么地方? 

答案:这次内存没有新增对象,还是之前的2个对象,一个在String pool,一个在Heap.

分析:当执行完语句(2)时候,在内存里面一个对象都没有创建。当我们定于语句(2)的时候,如果我们用字符串的常量值给s2赋值的话,那么首先jvm还是从string pool里面去查找有没有内容为abc的这样一个对象存在,我们在语句(1)执行完毕后,string pool里面就已经存在了abc的对象,那么这次就不会再在string pool里面去生成内容为abc的字符串对象了,而是会使用已经存在String Pool里面的内容为abc的字符串对象,并且会将s2这个引用指向String Pool里面的内容为abc的字符串对象,s2存放的是String Pool里面的内容为abc的字符串对像的地址。也就是说当你使用String s2 = "abc",即使用字符串常量("abc")给定义的引用(str2)赋值的话,那么它首先是在String Pool里面去找有没有内容为abc的字符串对象存在,如果有的话,就不用创建新的对象,直接引用String Pool里面已经存在的对象;如果没有的话,就在 String Pool里面去创建一个新的对象,接着将引用指向这个新创建的对象。所以,当执行完语句(2)时内存里面一共有2个对象,它们的内容分别都是abc,在String Pool里面一个内容abc的对象,在Heap里面有一个内容为abc的对象。

问题3:当执行完语句(3)时,在内存里面一共有几个对象?它们是什么?在什么地方? 

答案:再原来的基础上新增了一个对象,现在一个3个对象,分别都是abc,一个在String pool,两个在Heap.

当执行完语句(3)时,其执行过程是这样的:它首先在String Pool里面去查找有没有内容为abc的字符串对象存在,发现有这个对象存在,它就不去创建 一个新的对象。接着执行new...,只要在java里面有关键字new存在,不管内容是否相同,都表示它将生成一个新的对象,new多少次,就生成多少个对象,而且新生成的对象都是在Heap里面,所以它会在Heap里面生成一个内容为abc的对象,并且将它的地址赋给了引用s3,s3就指向刚在Heap里面生成的内容为abc的对象。所以,当执行完语句(3)时,内存里面一共有3个对象,其中包含了在String Pool里面一个内容为abc的字符串对象和在Heap里面包含了两个内容为abc的字符串对象。 

问题4:当执行完语句(4)(5)(6)后,它们的结果分别是什么? 

在java里面,对象用"=="永远比较的是两个对象的内存地址,换句话说,是比较"=="左右两边的两个引用是否指向同一个对象。对于java里面的8种原生数据类型来说,"=="比较的是它们的字面值是不是一样的;对引用类型来说,比较的是它们的内存地址是不是一样的。在语句(1)(2)(3)中,由于s1、s2、s3指向不同的对象,它们的内存地址就不一样,因此可以说当执行完语句(4)(5)(6),它们返回的结果都是false。 

String a="abcd";  String b="abcd" ;  a==b 为true 。因为a和b的引用指向都是string pool里面的通一个值。

问题5:当执行完语句(7)(8)(9)后,它们的结果分别是什么? 

首先,s1这个对象指向的是堆中第一次new...生成的对象,当调用 intern 方法时,如果String Pool已经包含一个等于此 String 对象的字符串(该对象由equals(Object)方法确定),则返回指向String Pool中的字符串对象的引用。因为String Pool中有内容为abc的对象,所以s1.intern()返回的是String Pool中的内容为abc的字符串对象的内存地址,而s1却是指向Heap上内容为abc的字符串对象的引用。因而,两个引用指向的对象不同,所以,s1 == s1.intern() 为false,即语句(7)结果为false。 

对于s2.intern(),它还是会首先检查String Pool中是否有内容为abc的对象,发现有,则将String Pool中内容为abc的对象的地址赋给s2.intern()方法的返回值。因为s2和s2.intern()方法的返回值指向的是同一个对象,所以,s2 == s2.intern()的结果为true,,即语句(8)结果为true。 

对于s1.intern(),它首先检查String Pool中是否有内容为abc的对象,发现有,则将String Pool中内容为abc的对象的赋给s1.intern()方法的返回值。对于s2.intern(),首先检查String Pool中是否有内容为abc的对象,发现有,则将String Pool中内容为abc的对象的地址赋给s2.intern()方法的返回值。因为两者返回的地址都指向同一个对象,所以,s1.intern() == s2.intern()的结果为true,,即是语句(9)结果为true。 

因此,当执行完语句(7)(8)(9)后,它们的结果分别是false、true、true。 

问题6:当执行完语句(13)(14) (15)(16)后,它们的结果分别是什么? 

hello == "hello"引用hello指向的对象就是String Pool中的“hello”,即语句(13)的结果为true。 

hello == "hel" + "lo"当加号两边都是常量值时,就会组成一个新的常量值"hello"在String Pool里面,如果String Pool已经有相同内容的就不会再创建,则直接返回String Pool里面的内容为"hello"的字符串对象的内存地址,所以,hello == "hel" + "lo"结果为true。 

hello =="hel" + lo 当加号两边有一个不是常量值,会在堆里面创建一个新的"hello"对象,一个在String Pool中,一个在Heap中,故输出false 。 

hel + lo 同上,输出false。 

因此,当执行完语句(7)(8)(9)后,它们的结果分别是true、true、false、false。 

从intern()方法看String字符串进入常量池的时机

在String类中有这么一个方法intern(),我们先来解释下这个方法,在调用"ab".intern()方法的时候会返回"ab",但是这个方法会首先检查字符串池中是否有"ab"这个字符串,如果存在则返回这个字符串的引用,否则就将这个字符串添加到字符串池中,然会返回这个字符串的引用【这里就有可能是返回的Heap堆内存的地址引用】。是不是有点晕?没事,来一段代码解释下。

public static void main(String[] args) {
        String s1=new String("he")+new String("llo");     // 1
        s1.intern();                                      // 2
        String s2="hello";                                // 3
        System.out.println(s1==s2);                       // 4
}

我们一行行来看。执行第1行代码时,首先会在堆中创建"he"和"llo"的对象,然后再将它们引用保存到字符串常量池中,然后有个+号对吧,内部是创建了一个StringBuilder对象,一路append,最后调用StringBuilder对象的toString方法得到一个String对象(内容是hello,注意这个toString方法会new一个String对象),并把它赋值给s1,因此s1表示堆中对象"hello"的引用。注意啊,没有把hello的引用放入字符串常量池。

    然后来到第2行,执行s1.intern(),jvm首先会到字符串常量池中寻找字符串"hello",发现并不存在,这时jvm会将s1对象的引用保存到字符串常量池中【常量池就有了“hello”了】,然后返回这个引用,但这个引用没有被接收,所以没有用。

    到了第3行,这时字符串常量池中已经有"hello"了,直接用。

    第4行,s1表示在堆中的对象"hello"的引用,而s2拿到的"hello"则是堆中"hello"对象在字符串常量池中的一个引用,所以它们指向了同一个对象,所以返回为true。

String s1=new String("he")+new String("llo");   // 1. 新建一个引用s1指向堆中的对象s1,值为"hello"
String s2=new String("h")+new String("ello");   // 2. 新建一个引用s2指向堆中的对象s2,值为"hello"
String s3=s1.intern();  // 3. 执行s1.intern()会在字符串常量池中新建一个引用"hello",该引用指向s1在堆中的地址,并新建一个引用s3指向字符串常量池中的"hello"
String s4=s2.intern();  // 4. 执行s2.intern()不会在字符串常量池中创建新的引用,因为"hello"已存在,因此只执行了新建一个引用s4指向字符串常量池中"hello"的操作,而这个引用又指向了s1指向的对象
System.out.println(s1==s3); //[true] s3和s4指向的都是字符串常量池中的"hello",而这个"hello"都指向堆中s1的地址,因此下面两句代码都为true
System.out.println(s1==s4); //[true] 
System.out.println(s2 == s3);// [false] s3和s4最终关联堆中的地址是对象s1,因此下面两句为false
System.out.println(s2 == s4); //[false] 

 

再来看上面这几行代码。第1行首先在堆中创建"he"和"llo"的对象,并将它们的引用放入字符串常量池,然后在堆中创建一个"hello"对象,没有放到字符串常量池,s1指向这个heap位置的"hello"对象。

    第2行在堆中创建"h"和"ello"对象,并放入字符串常量池,然后在堆中创建一个"hello"对象,没有放到字符串常量池,s2指向这个heap位置的"hello"对象。

    第3行,字符串常量池里没有"hello",因此会把s1指向的堆中的"hello"对象的引用放入字符串常量池(也就是说字符串常量池中的引用和s1指向了同一个对象),然后把这个引用返回给了s3,所以呢执行s3 == s1为true;

    第4行,字符串常量池里已经有"hello"了,因此直接将它返回给了s4,所以s4 == s1也为true。

    至于s2 == s3和s2 == s4为false则很明显了吧,s3和s4指向的字符串常量池中的引用和s1指向的对象是同一个,而s2则指向了另一个对象,因此返回false。

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

String s = new Sting("hello");
System.out.println(s.intern()==s);    // false

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值