样例如下:
        String s1 = new StringBuilder("12").append("ab").toString();
        System.out.println(s1.intern() == s1); //true        String s2 = new StringBuilder("ja").append("va").toString();
        System.out.println(s2.intern() == s2); //false        String s3 = new StringBuilder().append("12ab").toString();
        System.out.println(s3.intern() == s3); //false问题:
     1、s1  不应该是指向堆内存的地址吗? 为什么会与 s1.intern() 指向相同?
     2、s1、s2 调用形式形同,结果不同?
     3、s1、s3不同?看StringBuilder带String的构造方法,也是调用的append方法,不理解,求指教。谢谢!!!

解决方案 »

  1.   

    jdk1.8 版本
      

  2.   

    平时不这样用,具体原理也不敢说,能确定的一点是这个代码必然会被优化. 
    并且,如果只看反编译的结果可能达不到这种细节的粒度
    但是看一下字节码可能会有帮助,不过我懒得看了.  javap -verbose xxx.class另外可以和java6的情况比较一下.
      

  3.   

    String的intern()方法会得到字符串对象在常量池中和它相等的字符串的引用,如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用,所以(s1.intern() == s1)的结果为true。JDK 1.7及以后的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。而如果在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串的实例的引用,而StringBulder创建的字符串实例在Java堆上,所以必然不是同一个引用,(s1.intern() == s1)的结果将返回false。s2.intern() == s2时,由于java这个字符串已经存在在常量池了,java虚拟机会自动调用System类,System类会调用了initializeSystemClass方法,从而在此方法中调用了Version对象的init静态方法sun.misc.Version.init();Version类定义的私有静态字符串常量有这个 java  ,所以是false
    s3.intern() == s3,由于 s1的时候已经将这个字符串加入常量池了,所以也是false;
      

  4.   

    谢谢您的反馈,intern方法又查询了一下。不过,还是有点疑问,StrungBuilder中toString方法是return 了一个new String,我理解的这个s1应该还是指向了堆内存地址;另外:对于s1、s3的事情,我把s3那行提到s1行前面,这时侯三个结果都是false了。jdk1.8环境,
      

  5.   


    你把上面三句可以理解为下面的语句
    s1=new String("12")+new String("ab");
    s2=new String("ja")+new String("va");
    s3=new String("12ab");
      

  6.   

    首先明确下, HotSpot VM 在JDK 8 以前版本方法区(包含运行时常量池,其中就有字符串常量池)都是用的堆中的永久代(permanent generation)来实现,JDK 8及以后开始使用元空间(MetaSpace)来实现.从JDK 7 开始 HotSpot VM 已经把interned String (字符串常量池底层保存在StringTable中)从堆的永久代(permanent generation)挪到了堆中(包含年轻代和老年代   young and old generations),也就是说字符串常量池是是放在堆(年轻代和老年代)中的.具体描述可以看Oracle公布的资料https://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html  注意搜索下:6962931,这是相关的内容.字符串常量池中保存的是多个相同的String实例首个被intern的那个实例的引用,比如有多条语句声明了String实例"a",调用了intern()方法时,返回的是常量池中保存的是首次出现的"a"的引用.
            String a = "a";
            String b = "a";
            String c = "a";        System.out.println(a.intern() == a);
            System.out.println(a.intern() == b);
            System.out.println(a.intern() == c);        //输出了 true  true  true
    这里可以从专业的JVM 开发大牛 R大  的以前帖子里面了解到,https://www.iteye.com/topic/1112592?page=3,搜索:RednaxelaFX.注意看下R大的回答.再来说下String.intern()方法.比如说String实例"demo"来调用这个方法时,会查看池中所有引用的String实例有没有与"demo"相同,也就是用equals方法比较为true,如果没有,就保存"demo"的引用并返回"demo"的引用,如果有,就返回保存的引用.这个可以去看源码的注释.根据上面知道的在JDK 8 HotSpot VM环境下来分析下楼主的这几个问题.
    1.
       
            String s1 = new StringBuilder("12").append("ab").toString();
            System.out.println(s1.intern() == s1); //true通过StringBuilder 拼接了"12"和"ab" 得到了"12ab", 此时堆内存中的首次出现了"12ab"这个String实例.这个实例是在堆中的young generation里面的, s1保存的是对这个实例的引用,调用s1.intern()时,池中无与"12ab"相同内容的字符串实例引用,所以保存这个引用并返回了引用.使用 == 判断引用地址是否相等,结果为true
    2.  
            String s2 = new StringBuilder("ja").append("va").toString();
            System.out.println(s2.intern() == s2); //false4楼的同学已经提到了,Version类定义的私有静态字符串常量有String实例"java",会在jvm启动的时候加载到运行时常量池中.通过StringBuilder会生成一个新的String实例"java"保存在堆内存中,s2保存对这个实例的引用.虽然常量池中引用的实例"java"也在堆内存中,但是并不是同一个实例,相应的他们的引用也不一样,调用s2.intern()时返回的是Version类加载到运行时常量池中的引用,而不是s2指向的.此时用  ==  判断引用地址是否相同,结果为false.3.
            String s3 = new StringBuilder().append("12ab").toString();
            System.out.println(s3.intern() == s3); //false这个例子的分析与第二种情况类似,在常量池中已经有了"12ab"实例的引用,这个引用就是append()方法传入的"12ab"的引用 StringBuilder创建一个新的实例,在堆内存中,s3保存此实例的引用,与常量池中的引用不一致,通过== 判断,结果为false.
    至于你后面说把3放到1前面,所有的结果都为false了,通过上面的分析答案是很明显.类同2,3情况分析.
      

  7.   

      String s1 = new StringBuilder("12").append("ab").toString();分析下这个代码,可以理解为
    String a = "12";
    String b = "ab";
    String s1 = new StringBuilder(a).append(b).toString();
    这样常量池里就已经存在了“12”“ab”,但是不存s1的“12ab”,所以s1==s1.intern(),为true;再看
    String s3 = new StringBuilder().append("12ab").toString();
    分解为:
    String a = "12ab";
    String s3 = new StringBuilder().append(a).toString();
    这时常量池已经存在“12ab”,s3的12ab已经存在了
      

  8.   

    楼上2位对第3句解释的都有问题,不是该字符串在常量池中已经存在了,常量池不存在的一样是false,是因为s3运算根本没有引用到常量池。String s3 = new StringBuilder().append("34cd").toString();
            System.out.println(s3.intern() == s3); //false
      

  9.   

    电脑端找不到,回复评论的按钮,手机端回复了几次,编辑错误,一个用户只能回复3次,这个吐槽一下
    回归正题:
    我又写了2个单独的测试类:
    1、
    public class Test1 {
        public static void main(String[] args) {
            String s1 = new StringBuilder("12").append("ab").toString();
            System.out.println(s1.intern() == s1); //true    }
    }
    2、public class Test2 {
        public static void main(String[] args) {
        String s1 = new StringBuilder().append("12ab").toString();
        System.out.println(s1.intern() == s1); //false
        }
    }区别就是调用了不同的构造方法,一个true、一个false。这个也请看一下,谢谢。
    如果对1、2各追加一个append方法,结果都是true了
    1、public class Test1 {
        public static void main(String[] args) {
            String s1 = new StringBuilder("12").append("ab").append("cd").toString();
            System.out.println(s1.intern() == s1); //true    }
    }
    2、public class Test2 {
        public static void main(String[] args) {
        String s1 = new StringBuilder().append("12ab").append("cd").toString();
        System.out.println(s1.intern() == s1); //true
        }
    }    }
    }
    现在的疑问是:
    调用StringBuilder空参的append和有参的结果不一样,当追加至2个append方法时,结果一样,都是true。
    请各位再给看一下。
      

  10.   

    s3应该不是访问的常量池,单独写个类运行也是false,麻烦再看一下,谢谢~~~
      

  11.   


    其实我前面已经说过了当StringBuilder非空参时,如第1句,第2句
    相当于下面的语句,如果后面还有append就再加一个new String
    s1=new String("12")+new String("ab");
    s2=new String("ja")+new String("va");而StringBuilder空参时,如第3句如果后面只有一个append的话就相当于
    s3=new String("12ab");这时候只是相当于赋值语句,不会在常量池中计算,所以不会引入常量池的值。
    如果后面再加append话就和上面一样了。只不过当你相加后的字符串如果在常量池中已经有和没有结果会不一样。
      

  12.   

      String s1 = new StringBuilder("12").append("ab").toString();分析下这个代码,可以理解为
    String a = "12";
    String b = "ab";
    String s1 = new StringBuilder(a).append(b).toString();
    这样常量池里就已经存在了“12”“ab”,但是不存s1的“12ab”,所以s1==s1.intern(),为true;再看
    String s3 = new StringBuilder().append("12ab").toString();
    分解为:
    String a = "12ab";
    String s3 = new StringBuilder().append(a).toString();
    这时常量池已经存在“12ab”,s3的12ab 你还是没有理解这个分析;
    String s3 = new StringBuilder().append("12ab").toString();
    这样时,常量池会先出现“12ab”,而这时s3是“12ab”,发现常量池已经存在该字符,不会重新创建了,s3.intern()返回的是常量池地址,s3是new StringBulider()的内存地址,所以是false;String s3 = new StringBuilder("12").append("ab").append("ed").toString();
    这种时候
    常量池中出现的是  “12”“ab”“ed”,而此时s3是“12abed”,发现常量池中不存在该字符,然后将new StringBuilder()内存地址记录下来,s3.intern()返回的是new StringBuilder()的内存地址,所以true;
      

  13.   


    当.append("34dc")时,"34dc"就是先被创建到常量池,怎么能说没用到呢?然后结果是false,哪里不对吗?
      

  14.   


    当.append("34dc")时,"34dc"就是先被创建到常量池,怎么能说没用到呢?然后结果是false,哪里不对吗?不是在常量池创建的,都说了因为空构造+append相当于 String s3=null;这里只是初始化了空间但是没有内容,然后append加入字符串时是直接写入了这个空间内,所以根本没有用到常量池,s3是堆上的地址,这时候你把s3放入常量池s3.intern()始终不会和s3相等。如果再多一个append就和1,2没区别了。
    String s3 = new StringBuilder().append("34cd").append("56ef").toString();
      

  15.   

    我可能说的不是很准确看下源码就是知道了  public StringBuilder() {
            super(16);
        }
      public StringBuilder(String str) {
            super(str.length() + 16);
            append(str);
        }
    所以  new StringBuilder(“12cd”)等价于 new StringBuilder().append("12cd")
      

  16.   

    不管是new StringBuilder(),还是append()也好,只要其中出现 字符串,比如new StringBuilder(“abdf”) 或者append("abdf"); “abdf”就会出现在常量池,StringBuilder 不能凭空变个字符串给你你可以理解一下,String s = new String("asd");创建了几个对象,创建在哪里了
      

  17.   

    java文档写的很清楚
    https://docs.oracle.com/javase/8/docs/api/index.htmlWhen the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.如果常量池已存在和字符串对象的内容一样的对象话,则返回常量池对象(的引用)
    如果常量池里还不存在的话,就把字符串对象加到常量池,并返回该对象的引用根据文档
    String s1 = new StringBuilder("12").append("ab").toString();
    String s2 = "12ab"; //在intern方法前后,加上常量池“12ab”对象引用的代码,就明白了
    System.out.println(s1.intern() == s1); //true
    //String s2 = "12ab";至于s3和s1为什么不一样
    String s3 = new StringBuilder().append("12ab").toString(); //因为在这里常量池就已经创建“12ab”了,不管你换成什么内容,除非把字符串拆开写,和s1一样
    System.out.println(s3.intern() == s3); //false
      

  18.   

    如果照你的理论,常量池有的对象就会false,那下面的问什么会true。
    String s = new String("abc")+new String(“de”);
    System.out.println(s.intern()==s);//true
      

  19.   

    你别问我创建了几个对象,创建再哪里 ?
    你好好理解一下下面的不同。
    String s1="abc";
    String s2=new String("abc");
    System.out.println(s1==s2);//false
      

  20.   

    所以我说s3引用的地址根本不在常量池,而不管s3的值在常量池内有还是没有,s3.intern()的引用地址一定是常量池的地址,2者永远不会相等的,而碰巧楼主的s3的值常量池中是有的,因此我说第3句解释的有问题。
      

  21.   

    说一下我的想法,String s = new String("asd");不考虑S前提下,如果字面量"asd"不在常量池中(常量池保存的也是引用),创建2个对象;如果在常量池中,创建一个对象;对于:String s1="abc";
    String s2=new String("abc");
    System.out.println(s1==s2);//false ,我理解的是字符串字面量是一种隐式创建String对象,S1指向的是常量池,s2指向的是堆,== 比较地址,所以false。我翻看了一下java se8 虚拟机规范《第5章 5.1 运行时常量池》里面一段话是这么解释的:“为了得到字符常量,Java虚拟机需要检查CONSTANT_String_info结构中的码点序列。如果某String实例所包含的Unicode码点序列与CONSTANT_String_info结构所给出的序列相同,而之前又曾在该实例上面调用过String.intern()方法,那么此次字符常量获取的结果将是一个指向相同String实例的引用。否则,会创建一个新的String实例,其中包含由CONSTANT_String_info结构所给出的Unicode码点序列;字符常量获取的结果是指向那个新String实例的引用。最后,新String实例的intern()方法被Java虚拟机自动调用。”,就是这个"新String实例的intern()方法被Java虚拟机自动调用",那在new StringBuilder时,StringBuilder里面也是return new String...,按理说intern方法已经被执行了,为什么会得到false的结果呢。有不对的地方,请各位批评指正,也请大家再看一下这个问题,谢谢~~~
      

  22.   

    回复得晚了,本来也想整理下jvms8和 jsl8中的原文来回答问题,但是有些问题你已经自己找到答案了.
    现在说下你最后的问题,你引用的段话是 jvm  运行时常量池从Class文件的常量池(constant pool)中获取字符串字面常量(String literal) 引用的对象的方法,注意是仅仅针对String literal ,那什么又是String literal,在jsl8的3.10.5 中有定义: String literal是用由双引号括起来的0个或者多个字符构成的,这些字符可以是转义字符.,原文是:"A string literal consists of zero or more characters enclosed in double quotes.Characters may be represented by escape sequences (§3.10.6)".就是平时开发中很常见的直接出现在代码中的"0001"类似的字符串.碰到了String literal,用你说的这个方法,先检查常量池中是否有内容相同的String 实例的引用,如果有就直接返回这个引用,如果没有,在堆中新建一个String 实例,返回这个实例的引用,jvm自动调用intern()方法把这个引用保存到常量池中.所以直接用"123"给多个String 变量赋值,全部变量用 == 判断时一直都是true,这段代码就像我之前回复的那样.现在来说你问的为什么调用String s2 =  new String ("asd");s1 == s2 为false,在s2变量赋值时,new 操作符会在堆中再重新开辟一块内存,创建一个String 实例,这个实例的内容与"asd"所指向的String 实例 是完全一样的,但是这两个实例的内存地址是完全不同的.所以用 == 判断的时候,结果为false.可以用以下的图来说明.
      

  23.   

    如果照你的理论,常量池有的对象就会false,那下面的问什么会true。
    String s = new String("abc")+new String(“de”);
    System.out.println(s.intern()==s);//true还要怎么说?
    String s = new String("abc")+new String(“de”);
    常量池中创建 “abc”“de”两个字符串,s 最后的值是“abcde”,s.intern()发现常量池中不存在该值,赋值s的现有内存地址保存在常量池作为“abcde”的引用,所以s.intern()==s是true;
    String s = new String("abc")+new String(“de”);
    System.out.println(s.intern()==s);//true还不明白做个测试,
    String s = new String("abc")+new String(“de”);
    String s1 = “abcde”;//这里将abcde在常量池创建
    System.out.println(s.intern()==s);//false   //发现常量池中存在“abcde”s.intern()返回常量池现有引用,所以是false
      

  24.   


    我最后一次回复你,我一直说的是第3句的解释,不知道为何又被你扯回第2句去了。
    String s1=new StringBuilder("12").append("34").toString();
    System.out.println(s1.intern() == s1); //true
    String s2=new StringBuilder("5678").toString();
    System.out.println(s2.intern()==s2);//false
      

  25.   


    而如果在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串的实例的引用,而StringBulder创建的字符串实例在Java堆上,所以必然不是同一个引用,(s1.intern() == s1)的结果将返回false。s2.intern() == s2时,由于java这个字符串已经存在在常量池了,java虚拟机会自动调用System类,System类会调用了initializeSystemClass方法,从而在此方法中调用了Version对象的init静态方法sun.misc.Version.init();Version类定义的私有静态字符串常量有这个 java  ,所以是false
    s3.intern() == s3,由于 s1的时候已经将这个字符串加入常量池了,所以也是false;