网线水晶头接法图解,东莞seo网络营销,开发第一个app应用程序,微信公众号平台入口官网如何证明 new String 创建了 N 个对象#xff1f;
我想所有 Java 程序员都曾被这个 new String 的问题困扰过#xff0c;这是一道高频的 Java 面试题#xff0c;但可惜的是网上众说纷纭#xff0c;竟然找不到标准的答案。有人说创建了 1 个对象#xff0c;也有人说创建了…如何证明 new String 创建了 N 个对象
我想所有 Java 程序员都曾被这个 new String 的问题困扰过这是一道高频的 Java 面试题但可惜的是网上众说纷纭竟然找不到标准的答案。有人说创建了 1 个对象也有人说创建了 2 个对象还有人说可能创建了 1 个或 2 个对象但谁都没有拿出干掉对方的证据这就让我们这帮吃瓜群众们陷入了两难之中不知道到底该信谁得。
但是今天老王就斗胆和大家聊聊这个话题顺便再拿出点证据。
以目前的情况来看关于 new String(xxx) 创建对象个数的答案有 3 种
有人说创建了 1 个对象有人说创建了 2 个对象有人说创建了 1 个或 2 个对象。
而出现多个答案的关键争议点在「字符串常量池」上有的说 new 字符串的方式会在常量池创建一个字符串对象有人说 new 字符串的时候并不会去字符串常量池创建对象而是在调用 intern() 方法时才会去字符串常量池检测并创建字符串。
那我们就先来说说这个「字符串常量池」。
字符串常量池
字符串的分配和其他的对象分配一样需要耗费高昂的时间和空间为代价如果需要大量频繁的创建字符串会极大程度地影响程序的性能因此 JVM 为了提高性能和减少内存开销引入了字符串常量池Constant Pool Table的概念。
字符串常量池相当于给字符串开辟一个常量池空间类似于缓存区对于直接赋值的字符串String s“xxx”来说在每次创建字符串时优先使用已经存在字符串常量池的字符串如果字符串常量池没有相关的字符串会先在字符串常量池中创建该字符串然后将引用地址返回变量如下图所示
 以上说法可以通过如下代码进行证明
public class StringExample {public static void main(String[] args) {String s1 Java;String s2 Java;System.out.println(s1 s2);}
}以上程序的执行结果为true说明变量 s1 和变量 s2 指向的是同一个地址。
在这里我们顺便说一下字符串常量池的再不同 JDK 版本的变化。
常量池的内存布局
从JDK 1.7 之后把永生代换成的元空间把字符串常量池从方法区移到了 Java 堆上。
JDK 1.7 内存布局如下图所示  JDK 1.8 内存布局如下图所示  JDK 1.8 与 JDK 1.7 最大的区别是 JDK 1.8 将永久代取消并设立了元空间。官方给的说明是由于永久代内存经常不够用或发生内存泄露会爆出 java.lang.OutOfMemoryError: PermGen 的异常所以把将永久区废弃而改用元空间了改为了使用本地内存空间官网解释详情http://openjdk.java.net/jeps/122
答案解密
认为 new 方式创建了 1 个对象的人认为new String 只是在堆上创建了一个对象只有在使用 intern() 时才去常量池中查找并创建字符串。
认为 new 方式创建了 2 个对象的人认为new String 会在堆上创建一个对象并且在字符串常量池中也创建一个字符串。
认为 new 方式有可能创建 1 个或 2 个对象的人认为new String 会先去常量池中判断有没有此字符串如果有则只在堆上创建一个字符串并且指向常量池中的字符串如果常量池中没有此字符串则会创建 2 个对象先在常量池中新建此字符串然后把此引用返回给堆上的对象如下图所示 
老王认为正确的答案创建 1 个或者 2 个对象。
技术论证
解铃还须系铃人回到问题的那个争议点上new String 到底会不会在常量池中创建字符呢我们通过反编译下面这段代码就可以得出正确的结论代码如下
public class StringExample {public static void main(String[] args) {String s1 new String(javaer-wang);String s2 wang-javaer;String s3 wang-javaer;}
}首先我们使用 javac StringExample.java 编译代码然后我们再使用 javap -v StringExample 查看编译的结果相关信息如下
Classfile /Users/admin/github/blog-example/blog-example/src/main/java/com/example/StringExample.classLast modified 2020年4月16日; size 401 bytesSHA-256 checksum 89833a7365ef2930ac1bc3d7b88dcc5162da4b98996eaac397940d8997c94d8eCompiled from StringExample.java
public class com.example.StringExampleminor version: 0major version: 58flags: (0x0021) ACC_PUBLIC, ACC_SUPERthis_class: #16 // com/example/StringExamplesuper_class: #2 // java/lang/Objectinterfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:#1 Methodref #2.#3 // java/lang/Object.init:()V#2 Class #4 // java/lang/Object#3 NameAndType #5:#6 // init:()V#4 Utf8 java/lang/Object#5 Utf8 init#6 Utf8 ()V#7 Class #8 // java/lang/String#8 Utf8 java/lang/String#9 String #10 // javaer-wang#10 Utf8 javaer-wang#11 Methodref #7.#12 // java/lang/String.init:(Ljava/lang/String;)V#12 NameAndType #5:#13 // init:(Ljava/lang/String;)V#13 Utf8 (Ljava/lang/String;)V#14 String #15 // wang-javaer#15 Utf8 wang-javaer#16 Class #17 // com/example/StringExample#17 Utf8 com/example/StringExample#18 Utf8 Code#19 Utf8 LineNumberTable#20 Utf8 main#21 Utf8 ([Ljava/lang/String;)V#22 Utf8 SourceFile#23 Utf8 StringExample.java
{public com.example.StringExample();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack1, locals1, args_size10: aload_01: invokespecial #1 // Method java/lang/Object.init:()V4: returnLineNumberTable:line 3: 0public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack3, locals4, args_size10: new #7 // class java/lang/String3: dup4: ldc #9 // String javaer-wang6: invokespecial #11 // Method java/lang/String.init:(Ljava/lang/String;)V9: astore_110: ldc #14 // String wang-javaer12: astore_213: ldc #14 // String wang-javaer15: astore_316: returnLineNumberTable:line 5: 0line 6: 10line 7: 13line 8: 16
}
SourceFile: StringExample.java备注以上代码的运行也编译环境为 jdk1.8.0_101。 其中 Constant pool 表示字符串常量池我们在字符串编译期的字符串常量池中找到了我们 String s1 new String(javaer-wang); 定义的“javaer-wang”字符在信息 #10 Utf8 javaer-wang 可以看出也就是在编译期 new 方式创建的字符串就会被放入到编译期的字符串常量池中也就是说 new String 的方式会首先去判断字符串常量池如果没有就会新建字符串那么就会创建 2 个对象如果已经存在就只会在堆中创建一个对象指向字符串常量池中的字符串。
那么问题来了以下这段代码的执行结果为 true 还是 false
String s1 new String(javaer-wang);
String s2 new String(javaer-wang);
System.out.println(s1 s2);既然 new String 会在常量池中创建字符串那么执行的结果就应该是 true 了。其实并不是这里对比的变量 s1 和 s2 堆上地址因为堆上的地址是不同的所以结果一定是 false如下图所示
 从图中可以看出 s1 和 s2 的引用一定是相同的而 s3 和 s4 的引用是不同的对应的程序代码如下
public static void main(String[] args) {String s1 Java;String s2 Java;String s3 new String(Java);String s4 new String(Java);System.out.println(s1 s2);System.out.println(s3 s4);
}程序执行的结果也符合预期 true false 扩展知识
我们知道 String 是 final 修饰的也就是说一定被赋值就不能被修改了。但编译器除了有字符串常量池的优化之外还会对编译期可以确认的字符串进行优化例如以下代码
public static void main(String[] args) {String s1 abc;String s2 ab c;String s3 a b c;System.out.println(s1 s2);System.out.println(s1 s3);
}按照 String 不能被修改的思想来看s2 应该会在字符串常量池创建两个字符串“ab”和“c”s3 会创建三个字符串他们的引用对比结果也一定是 false但其实不是他们的结果都是 true这是编译器优化的功劳。
同样我们使用 javac StringExample.java 先编译代码再使用 javap -c StringExample 命令查看编译的代码如下
警告: 文件 ./StringExample.class 不包含类 StringExample
Compiled from StringExample.java
public class com.example.StringExample {public com.example.StringExample();Code:0: aload_01: invokespecial #1 // Method java/lang/Object.init:()V4: returnpublic static void main(java.lang.String[]);Code:0: ldc #7 // String abc2: astore_13: ldc #7 // String abc5: astore_26: ldc #7 // String abc8: astore_39: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;12: aload_113: aload_214: if_acmpne 2117: iconst_118: goto 2221: iconst_022: invokevirtual #15 // Method java/io/PrintStream.println:(Z)V25: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;28: aload_129: aload_330: if_acmpne 3733: iconst_134: goto 3837: iconst_038: invokevirtual #15 // Method java/io/PrintStream.println:(Z)V41: return
}从 Code 3、6 可以看出字符串都被编译器优化成了字符串“abc”了。
总结
本文我们通过 javap -v XXX 的方式查看编译的代码发现 new String 首次会在字符串常量池中创建此字符串那也就是说通过 new 创建字符串的方式可能会创建 1 个或 2 个对象如果常量池中已经存在此字符串只会在堆上创建一个变量并指向字符串常量池中的值如果字符串常量池中没有相关的字符会先创建字符串在返回此字符串的引用给堆空间的变量。我们还将了字符串常量池在 JDK 1.7 和 JDK 1.8 的变化以及编译器对确定字符串的优化希望能帮你正在的理解字符串的比较。
最后的话 原创不易本篇近 3000 的文字描述以及大量精美的图片耗费了作者大概 5 个多小时的时间写作是一件很酷并且能帮助他人的事作者希望一直能坚持下去。如果觉得有用请随手点击一个赞吧谢谢。