• Java基础——字符串常量池遇到的坑


    原来学java的时候,这块就没怎么看,最近学多线程稍微仔细看了一下,遇到不少疑惑。
    参考了这篇博客String:字符串常量池

    问题一:String str1 = new String("abc"); 到底创建了几个对象?

    一般的回答

    2个,一个是在堆中new的String("abc")对象,一个是字符串常量池创建的"abc"。

    更严谨的说法

    1. 严谨的问法:
    • String str1 = new String("abc"); 运行时(包括类加载和程序执行)涉及几个String实例?
    1. 回答
    • 2个。一个是字符串字面量"abc"对应的,驻留在字符串常量池的实例(类加载时创建);一个是new String("abc")在堆中创建的,内容和"abc"相同的实例(程序运行时)
     Code:
          stack=3, locals=2, args_size=1
             0: new           #2                  // class java/lang/String
             3: dup
             4: ldc           #3                  // String abc
             6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
             9: astore_1
            10: return
    

    可以看到只有一个new,在堆中创建了String对象(Code:0:new),
    "abc"字面量实例在常量池中已经存在,所以只是把先前类加载中创建好的String("abc")实例的一个引用压入操作栈顶,并没有创建String对象。(Code:4:ldc)

    问题二:String str1 = new String("A"+"B"); 在字符串常量池中创建几个实例?

    错误的回答

    • 3个。"A"、"B"、"AB"。

    正确的回答

    • 1个。只有"AB"。

    查看字符串常量池:
    方法:javap -verbose XXX.class

    Constant pool:
       #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
       #2 = Class              #16            // java/lang/String
       #3 = String             #17            // AB
       #4 = Methodref          #2.#18         // java/lang/String."<init>":(Ljava/lang/String;)V
       #5 = Class              #19            // Test7
       #6 = Class              #20            // java/lang/Object
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               main
      #12 = Utf8               ([Ljava/lang/String;)V
      #13 = Utf8               SourceFile
      #14 = Utf8               Test7.java
      #15 = NameAndType        #7:#8          // "<init>":()V
      #16 = Utf8               java/lang/String
      #17 = Utf8               AB
      #18 = NameAndType        #7:#21         // "<init>":(Ljava/lang/String;)V
      #19 = Utf8               Test7
      #20 = Utf8               java/lang/Object
      #21 = Utf8               (Ljava/lang/String;)V
    

    可以看到只有一个"AB"。
    也可以通过字节码看到:

    Code:
          stack=3, locals=2, args_size=1
             0: new           #2                  // class java/lang/String
             3: dup
             4: ldc           #3                  // String AB
             6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
             9: astore_1
            10: return
    

    只有一个的原因

    • 编译时优化,会把"A"和"B"合并成一个"AB"保留到常量池。

    问题三:String str1 = new String("ABC") + "ABC"; 在字符串常量池中创建几个实例?

    错误的回答

    • 2个。"ABC"和"ABCABC"。

    正确的回答

    • 1个。只有"ABC"。
     Code:
          stack=4, locals=2, args_size=1
             0: new           #2                  // class java/lang/StringBuilder
             3: dup
             4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
             7: new           #4                  // class java/lang/String
            10: dup
            11: ldc           #5                  // String ABC
            13: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
            16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            19: ldc           #5                  // String ABC
            21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            27: astore_1
            28: return
    

    实际是创建了一个StringBuilder对象(Code:0:new),然后又创建了一个String对象(Code:7:new),
    接着把已经驻留在常量池中的"ABC"压入操作栈(Code:11:ldc),调用append方法。(重复1次)。
    最后调用toString方法获得合并后的String对象。
    也就是说创建了2个对象,在常量池中驻留了一个"ABC"字面量实例。

    在问题三的基础上,添上intern()方法会不会在常量池中创建"ABCABC"实例?

    答案

    不会。

    public class Test {
        public static void main(String[] args) {
            String str1 = new String("ABC") + "ABC";
            System.out.println(str1.intern() == str1);
        }
    }
    

    结果是true,可见并没有在常量池中创建"ABCABC"字面量实例。

    intern()方法

    • 如果在常量池当中没有字符串的引用,那么就会生成一个在常量池当中的引用,否则直接返回常量池中字符串引用。

    分析上面的代码:

    public class Test {
        public static void main(String[] args) {
            String str1 = new String("ABC") + "ABC";  //str1指向堆中合并后的String("ABCABC")对象
            System.out.println(str1.intern() == str1); //intern()方法:在常量池中找不到“ABCABC”这个常量对象(问题三已经说明),所以生成常量引用,和堆中那个对象的地址相同,也就是str1
        }
    }
    
    • 把代码稍作修改,intern()方法能找到"ABCABC"常量对象吗?
    public class Test {
        public static void main(String[] args) {
            String str1 = new String("ABC") + "ABC"; 
            System.out.println(str1.intern() == str1); 
            String str2 = "ABCABC";
            System.out.println(str1 == str2);
        }
    }
    

    结果是

    true
    true
    

    也就是说intern()方法仍然找不到"ABCABC"常量对象,并且str2随后在常量池中找到了"ABCABC"的引用,所以str1和str2都指向了一开始堆中合并后的String("ABCABC")对象。

    • 换一种写法,让intern()方法找到"ABCABC"常量对象
      String str2 = "ABCABC";移动到第一行:
    public class Test {
        public static void main(String[] args) {
            String str2 = "ABCABC";
            String str1 = new String("ABC") + "ABC"; 
            System.out.println(str1.intern() == str1);
            System.out.println(str1 == str2);
        }
    }
    

    结果是:

    false
    false
    

    原因:

    public class Test {
        public static void main(String[] args) {
            String str2 = "ABCABC"; //在常量池创建了"ABCABC"字面量实例,str2指向该实例
            String str1 = new String("ABC") + "ABC"; //在堆中得到一个合并的String("ABCABC")对象,str1指向它
            System.out.println(str1.intern() == str1); //intern()方法在常量池能找到"ABCABC"常量对象,直接返回它的引用,也就是str2,所以str1.intern() != str1
            System.out.println(str1 == str2); //str1.intern()和str2指向同一个对象,str1和str2指向不同对象
        }
    }
    

    总结

    网络上有很多人写博客,但是良莠不齐,有的写的很误导人,而且可能有错误,不认真思考的话很容易掉坑里。
    希望大家保持质疑的态度,多动手多思考,不要人云亦云。

    作者:lykxbg —— 来一块小饼干

    出处:http://www.cnblogs.com/lykxbg/

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    经典回溯问题--八皇后dfs递归回溯求解【DFS】
    CSP认证考试(第九次)第一题
    C++字符串和数字格式转化(使用sprintf()和sscanf()函数)
    2016蓝桥杯C++A组第六题 寒假作业【暴力搜索】
    先序非递归建立二叉树
    sqlsrv数据库复杂语句1
    tp5域名配置
    JavaScript使用 value 属性
    数据库随机查询6条数据
    文件目录问题
  • 原文地址:https://www.cnblogs.com/lykxbg/p/13656261.html
Copyright © 2020-2023  润新知