美文网首页
String到底相不相等?String初始化及String.in

String到底相不相等?String初始化及String.in

作者: kkyeer | 来源:发表于2019-04-08 00:23 被阅读0次

    引言

    在各种面试题中经常见到类似下述的面试题

    写出main方法的打印结果

        class test{        
            public static void main(String[] args){
                String s1 = new String("1") + new String("1") + new String("1");
                String s2 = new String("11") + new String("1");
                System.out.println(s1 == s2);
                s2.intern();
                String s3 = "111";
                System.out.println(s2 == s3);
                String s4 = s1.intern();
                System.out.println(s4 == s1);
        
                String s6 = new String("1");
                String s7 = "1";
                String s8 = s6.intern();
                System.out.println(s7 == s6);
                System.out.println(s8 == s6);
                System.out.println(s7 == s8);
            }
        }
    

    答案为:

    false
    true
    false
    false
    false
    true
    false
    false
    true
    

    此题要求对String类的引用有相关了解,提炼相关的要点如下

    要点列表

    1. 两个对象obj1和obj2,当且仅当obj1与obj2指向相同的引用(内存地址,对象地址)时,ojb1==obj2返回true,其他返回false
    2. String初始化时,若采用String s = "1";这种字面量直接赋值的形式,则过程为:
      判断字符串常量池中是否有值与字面量相同的引用,有的话s指向这个引用指向的对象,否则在堆上新建值为字面量的String对象,s指向新建的对象,并在字符串常量池中存储对应的引用
    3. String初始化时,若采用String s = new String("1");这种new对象的形式,或者使用其他带参构造器,则过程为:
      在堆上新建字符串实例,值为相关值,s直接指向堆上新增的这个对象
    4. String s = new String("1") + new String("1")等同于String s = new StringBuilder().append("1").append("1").toString();由于StringBuilder的toString方法也是调用的String的带参构造方法,
      因此在引用处理时,结果与3相同
    5. String类的intern()方法执行过程为:检查字符串常量区是否有相同值(hash)的引用,有的话返回此引用,否则将调用者放入字符串常量区,并返回调用者的引用

    实际代码分析

    如下为上述代码及每一行的分析

        String s1 = new String("1") + new String("1") + new String("1");
    
                                        ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
            上面这段代码,在运行时会在堆上创建对象"1"并在字符串常量池中创建对应的引用,
            同时由于字符串常量池中没有值为"111"的对象,因此会在堆上创建一个值为"111"的对象,
            s1指向刚创建的这个"111"对象,但不会在常量池中新创建值为"111"的引用,因此常量池中仍旧
            没有值为"111"的对象
    
                                       ↓↓↓↓↓↓↓↓内存状态↓↓↓↓↓↓↓↓↓
            --------------------------------------堆--------------------------------------------
            addr1("1")
            addr2("111")
            ------------------------------------------------------------------------------------
    
            ---------------------------------字符串常量池---------------------------------------
            addr1("1")
            ------------------------------------------------------------------------------------
    
            ---------------------------------------变量-----------------------------------------
            s1 -> addr2
            ------------------------------------------------------------------------------------
    
            String s2 = new String("11") + new String("1");
    
        /*
                                        ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
            上面这段代码,在运行时会在堆上创建对象"11"并在字符串常量池中创建对应的引用,
            同时由于字符串常量池中仍旧没有值为"111"的对象,因此会在堆上创建一个值为"111"的对象,
            s2指向刚创建的这个"111"对象,
            注意,此"111"对象地址与s1不同,此时在堆上存在两个值都为"111"的String对象
    
                                       ↓↓↓↓↓↓↓↓内存状态↓↓↓↓↓↓↓↓↓
            --------------------------------------堆--------------------------------------------
            addr1("1")
            addr2("111")
            addr3("11")
            addr4("111")
            ------------------------------------------------------------------------------------
    
            ---------------------------------字符串常量池---------------------------------------
            addr1("1")
            addr3("11")
            ------------------------------------------------------------------------------------
    
            ---------------------------------------变量-----------------------------------------
            s1 -> addr2
            s2 -> addr4
            ------------------------------------------------------------------------------------
    
            System.out.println(s1 == s2);
    
        /*
                                        ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
            根据上面的运行过程可知,s1和s2分别指向堆上两个对象,只是堆上的对象恰巧值均为"111",
            因此打印false
         */
        s2.intern();
        /*
                                        ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
            根据JDK7以后的实现,上面这句代码会检查字符串常量区,此时字符串常量区中没有值为"111"
            的引用,因此,会将s2的引用复制到字符串常量区
    
                                       ↓↓↓↓↓↓↓↓内存状态↓↓↓↓↓↓↓↓↓
            --------------------------------------堆--------------------------------------------
            addr1("1")
            addr2("111")
            addr3("11")
            addr4("111")
            ------------------------------------------------------------------------------------
    
            ---------------------------------字符串常量池---------------------------------------
            addr1("1")
            addr3("11")
            addr4("111")
            ------------------------------------------------------------------------------------
    
            ---------------------------------------变量-----------------------------------------
            s1 -> addr2
            s2 -> addr4
            ------------------------------------------------------------------------------------
         */
    
            String s3 = "111";
    
        /*
                                        ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
            执行了上述intern()过程后,字符串常量区中已有值为"111"的引用,根据实现,s3赋值为常量
            区中值为"111"对应的引用也就是s2指向的堆上那个对象的地址
    
                                       ↓↓↓↓↓↓↓↓内存状态↓↓↓↓↓↓↓↓↓
            --------------------------------------堆--------------------------------------------
            addr1("1")
            addr2("111")
            addr3("11")
            addr4("111")
            ------------------------------------------------------------------------------------
    
            ---------------------------------字符串常量池---------------------------------------
            addr1("1")
            addr3("11")
            addr4("111")
            ------------------------------------------------------------------------------------
    
            ---------------------------------------变量-----------------------------------------
            s1 -> addr2
            s2 -> addr4
            s3 -> addr4
            ------------------------------------------------------------------------------------
         */
    
            System.out.println(s2 == s3);
    
        /*
                                        ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
            s3和s2指向相同的堆上的对象,因此结果为true
         */
    
            String s4 = s1.intern();
    
        /*
                                        ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
            因为字符串常量区中已有值为"111"的引用,因此此方法不会对字符串常量区中的值产生影响,
            但因为inter()方法返回的是字符串常量区中的引用,因此s4指向s2对应的对象
    
                                       ↓↓↓↓↓↓↓↓内存状态↓↓↓↓↓↓↓↓↓
            --------------------------------------堆--------------------------------------------
            addr1("1")
            addr2("111")
            addr3("11")
            addr4("111")
            ------------------------------------------------------------------------------------
    
            ---------------------------------字符串常量池---------------------------------------
            addr1("1")
            addr3("11")
            addr4("111")
            ------------------------------------------------------------------------------------
    
            ---------------------------------------变量-----------------------------------------
            s1 -> addr2
            s2 -> addr4
            s3 -> addr4
            s4 -> addr4
            ------------------------------------------------------------------------------------
         */
    
            System.out.println(s4 == s1);
    
        /*
                                        ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
            s4和s1指向堆上的不同的对象,因此结果为false
         */
    
            String s6 = new String("1");
            String s7 = "1";
            String s8 = s6.intern();
    
        /*
                                        ↓↓↓↓↓↓↓↓解析↓↓↓↓↓↓↓↓↓
            根据上面的内存状态可知,s6仍旧指向堆上一个新建的值为"1"的对象,
            s6.inter()返回的是字符串常量区已有的值为"1"的对象地址,
            s8也等于这个地址
    
                                       ↓↓↓↓↓↓↓↓内存状态↓↓↓↓↓↓↓↓↓
            --------------------------------------堆--------------------------------------------
            addr1("1")
            addr2("111")
            addr3("11")
            addr4("111")
            addr5("1")
            ------------------------------------------------------------------------------------
    
            ---------------------------------字符串常量池---------------------------------------
            addr1("1")
            addr3("11")
            addr4("111")
            ------------------------------------------------------------------------------------
    
            ---------------------------------------变量-----------------------------------------
            s1 -> addr2
            s2 -> addr4
            s3 -> addr4
            s4 -> addr4
            s6 -> addr5
            s7 -> addr1
            s8 -> addr1
            ------------------------------------------------------------------------------------
    
    
         */
    
            System.out.println(s7 == s6);
            System.out.println(s8 == s6);
            System.out.println(s7 == s8);
    
        /*
            根据上面的内存状态可知,打印结果为:
            false
            false
            true
         */
    

    延申

    1. 由于使用有参构造器来初始化变量时,总会在堆上新建变量,因此在极限情况下,确有可能在初始化阶段造成OOM,解决方法是尽量使用字面量初始化字符串
    2. String内部字符串常量区的实现方式不同,openjdk中,实现类似HashMap,当放入过多常量时,插入与查找也会产生部分性能损耗,因此,调用String的intern()方法也要看情况确定

    相关文章

      网友评论

          本文标题:String到底相不相等?String初始化及String.in

          本文链接:https://www.haomeiwen.com/subject/malyiqtx.html