java不合法的常量字符_Java 字符串常量池遇到的坑

原来学java的时候,这块就没怎么看,最近学多线程稍微仔细看了一下,遇到不少疑惑。

参考了这篇博客String:字符串常量池

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

一般的回答

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

更严谨的说法

严谨的问法:

String str1 = new String("abc"); 运行时(包括类加载和程序执行)涉及几个String实例?

回答

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."":(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."":()V

#2 = Class #16 // java/lang/String

#3 = String #17 // AB

#4 = Methodref #2.#18 // java/lang/String."":(Ljava/lang/String;)V

#5 = Class #19 // Test7

#6 = Class #20 // java/lang/Object

#7 = Utf8

#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 // "":()V

#16 = Utf8 java/lang/String

#17 = Utf8 AB

#18 = NameAndType #7:#21 // "":(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."":(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."":()V

7: new #4 // class java/lang/String

10: dup

11: ldc #5 // String ABC

13: invokespecial #6 // Method java/lang/String."":(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指向不同对象

}

}

总结

网络上有很多人写博客,但是良莠不齐,有的写的很误导人,而且可能有错误,不认真思考的话很容易掉坑里。

希望大家保持质疑的态度,多动手多思考,不要人云亦云。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值