谢谢提醒,之前的文章写错了,但是还有一点不太清楚,解析字符串字面量时,究竟是先创建 String 对象,再到字符串常量池查找,还是先到字符串常量池查找,没有再创建 String 对象?
我觉得按正常人的思维,应是第 2 种,否则先创建 String 对象,万一字符串常量池已有值相同的字符串引用,就浪费了。当然也不排除第一种先创建 String 对象,才能调用 intern 方法的可能。不过底层多的是办法绕过去。
另外求职,意向软件测试实习,小白一枚。
之前自学 Java 时,都说好找工作,现在人太多了,风向又变了,说什么软件测试有很大缺口,赶紧来报培训班,之前面试也是,没想到是培训机构,反正作为不太优秀的我,就是被牵着鼻子走啊。
下面是正文:(肯定也有些小错误,但是契合已有例子问题不大)
字符串被使用前(执行 ldc 指令时)才去解析它。
String s1 = "hello"; String s2 = "hello"; System.out.println(s1 == s2);
# String s1 = "hello"; 0: ldc #7 // String hello 2: astore_1 # String s2 = "hello"; 3: ldc #7 // String hello 5: astore_2
所以 s1、s2 指向同一个对象,s1 == s2 为 true。
例 2:
String str = "abc";
String str2 = new String("abc");
System.out.println(str == str2);
# String str = "abc";
0: ldc #7 // String abc
2: astore_1
# String str2 = new String("abc");
3: new #9 // class java/lang/String
6: dup
7: ldc #7 // String abc
9: invokespecial #11 // Method java/lang/String."":(Ljava/lang/String;)V
12: astore_2
13: return
str、str2 指向的不是同一个对象,str == str2 为 false。
每个 Java 虚拟机必须维护一张内部列表 StringTable(内部结构是哈希表),也称字符串常量池,列出程序运行中已被 “拘留(intern)” 的字符串对象的引用,保证每个字符串对象在表中只出现一次。
可以通过 -XX:StringTableSize=60013 设置字符串常量池大小。
例 3:
String s1 = new String("hello");
String s2 = s1.intern();
System.out.println(s1 == s2);
JDK6 及之前,某引用调用 intern 方法时,若字符串常量池中已有指向值相同的字符串对象的引用,则直接返回池中引用。
若池中没有这样的引用,则在永久代中创建一个值相同的 String 对象,将引用拷贝到池中,并返回此引用。[5]
执行第一句代码,字符串常量池中无 “hello” 的引用,在永久代中创建 “hello” 对象,并将其引用放入池中,随后访问 “hello” 字面量都是返回此引用。
在堆中创建字符串对象,通过 init 方法传入 “hello” 引用完成实例变量的初始化,使得此字符串的值也为 “hello”。
然后将堆中对象的引用赋给 s1。
执行第 2 句代码,字符串常量池中已有值为 “hello” 的引用,直接返回此引用。
所以 s1 指向堆中的对象,s2 指向永久代的对象,s1 == s2 为 false。
String s1 = new String("hello");
String s2 = s1.intern();
System.out.println(s1 == s2);
JDK7 及以后,字符串常量池被移到堆中,intern 方法实现有所变化。
某引用调用 intern 方法时,之前部分一样…;若池中没有值相同的对象的引用,将此引用拷贝到池中,同时返回其引用。
所以过程同上,将永久代改为堆中。
例 4:
String s1 = new String(new char[] {'a', 'b', 'c'});// 值为 “abc” 的字符串
s1.intern();
String s2 = "abc";
JDK6:第 1 句代码在堆中创建了一个值为 “abc” 的字符串,引用赋给 s1。
第 2 句,池中无 “abc” 引用,于是在永久代中创建了一个新的 “abc” 对象,将新对象的引用放入池中,并返回此引用。
执行第 3 句代码时,在解析 “abc” 时,字符串常量池中已有值为 “abc” 引用,所以直接返回池中引用。所以 s1 == s2 为 false。
JDK7:第 2 句,池中无 “abc” 引用,将 s1 对象引用拷贝到池中,并返回此引用。
第 3 句,解析 “abc” 字面量时,在常量池中查找是否有 “hello” 的引用,发现有,直接返回池中引用。所以 s1 == s2 为 true。
将 String s1 = new String(new char[] {'a', 'b', 'c'}); 换成 String s1 = new String("a") + new String("bc"); 也是一样的效果,只要在 “abc” 字面量出现前,先让值为 “abc” 的引用加入常量池中就行。
例 5:
String s1 = new String(new char[] {'a', 'b', 'c'});// 值为 “abc” 的字符串
String s2 = "abc";
第一句在堆中创建了值为 “abc” 的字符串;
第二句,池中无 “abc” 引用,创建了一个新的 “abc” 对象。
所以 s1 == s2 为 false。
例 6:
final String s1 = "a";
String s2 = s1 + "bc";// 或换成 String s2 = "a" + "bc";
System.out.println("abc" == s2);
第二句对应的字节码指令:
0: ldc #7 // String abc 2: astore_1
对于只有常量和字面量的拼接,会自动优化成 String s2 = "abc";,所以 “abc” == s2 为 true。
若将 final 去掉,有变量参与的拼接,JDK8 及之前会调用 StringBuilder.append 方法拼接,最后调用 toString 方法在堆中创建 String 对象。
JDK8 后内部调用 java.lang.StringConcatHelper#simpleConcat 方法完成拼接,也是在堆中创建对象。
所以 “abc” == s2 为 false。



