一.常量池是什么
常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = “java”;可扩充( String.intern()方法 ),执行器产生的常量也会放入常量池,故认为常量池是JVM的一块特殊的内存空间。
二.题目
I.以下两种创建字符串对象的方式有什么不同?
String str1 = new String("abc");
String str2 = "abc";
答:
- 第一种方式先在堆(heap)中创建一个对象,然后在栈区创建一个String对象引用str1指向这个对象。若常量池中没有"abc"对象,则再创建一个"abc"对象放入常量池中。(运行时期创建)
- 第二种方式先在栈区创建一个String的对象引用str2,然后去常量池中寻找有没有"abc"对象,如果有,直接将str2指向它;如果没有,先创建字符串对象"abc"放入常量池中,再将str2指向常量池中的"abc"常量。(编译期间执行)
注意:如果不清楚java字符串常量池里存的是String对象还是引用?
请到这位大神的博客中查看:
https://blog.csdn.net/shy244/article/details/80994059
II.下面语句将会创建几个对象
String str1 = new String("A"+"B") ;
String str2 = new String("ABC") + "ABC" ;
- str1:
字符串常量池:“A”,“B”,“AB” : 3个
堆:new String(“AB”) :1个
引用: str1 :1个
总共 : 5个 - str2 :
字符串常量池:“ABC” : 1个
堆:new String(“ABC”) :1个
引用: str2 :1个
总共 : 3个
III.true or false
String str1 = "abc"; //pool
String str2 = "abc"; //pool
String str3 = new String("abc"); //heap
String str4 = new String("abc"); //heap
System.out.println(str1 == str2); //1
System.out.println(str2 == str3); //2
System.out.println(str3 == str4); //3
答:
true
false
false
- 创建String对象引用str1,查看常量池中是否有"abc"对象;不存在,所以创建"abc"对象并放入常量池中,最后将str1指向常量池中的"abc"对象。
- 创建String对象引用str2,发现常量池中已经有"abc"对象,直接将str2指向常量池中的对象。
- 先在堆(heap)中创建一个对象,然后在栈区创建一个String对象引用str3指向这个对象。发现常量池中有"abc",不再创建。
- 先在堆(heap)中再创建一个对象,然后在栈区创建一个String对象引用str4指向这个对象。发现常量池中有"abc",不再创建。
- 两个引用指向了同一个对象,返回true。
- str2指向常量池中对象,str3指向堆中对象,指的不是同一个对象,返回false。
- str3指向堆中对象,str3指向堆中对象,而两个指的不是同一个对象,返回false。
IV.true or false(接着上一题)
String str5 = str4; //heap
String str6 = str4.intern(); //pool
String str7 = str1; //pool
System.out.println(str4 == str5); //1
System.out.println(str4 == str6); //2
System.out.println(str1 == str2); //3
System.out.println(str1 == str7); //4
答:
true
false
true
true
注意:public String intern():
返回字符串对象的规范化表示形式
返回一个字符串,内容与此字符串相同,但一定取自具有唯一字符串的池(常量池)。也就是“有就拿,没有放进去再拿”。
- 创建str5引用并将其指向str4指向的对象,即堆中的一个"abc"对象。
- 创建str6引用并将其指向str4指向的对象,即堆中的另一个"abc"对象。
- 创建str7引用并将其指向str1指向的对象,即常量池中的"abc"对象。
- 两个引用指的都是堆中的同一个对象,即返回true。
- str4指的是堆中对象,而str6指的是常量池中的对象,即返回false。
- 两个引用指的都是常量池中的"abc"对象,即返回true。
- 两个引用指的都是常量池中的"abc"对象,即返回true。
V.true or false(接着上一题)
str4.intern();
System.out.println(str4 == str1); //1
str4 = str4.intern(); //pool
System.out.println(str4 == str1); //2
答:
false
true
- 返回了str4指向的字符串对象的规范化表示形式(即找到了该对象在常量池中所对应的对象),却没有让str4接收(指向该规范化对象),即str4指向的对象仍然是堆中的对象,而str1指向的是常量池中的"abc"对象,即返回false。
- 两个引用都指向了常量池中的"abc"对象,即返回true。
VI.true or false
String s1 = "Master";
String s2 = new String("Master");
final String s3 = "Master";
String s4 = "AndyMaster"; //pool
String s5 = "Andy" + "Master"; //pool
String s6 = "Andy" + s1; //heap
String s9 = "Andy" + s1; //heap
String s7 = "Andy" + s2; //heap
String s8 = "Andy" + s3; //pool
System.out.println(s4 == s5); //1
System.out.println(s4 == s6); //2
System.out.println(s4 == s7); //3
System.out.println(s4 == s8); //4
System.out.println(s6 == s9); //5
答:
true
false
false
true
false
- s4,s5指向的对象都在常量池中,返回true。
- “Andy” + s1;**只要s1是变量,不论s1指向池中的字符串对象还是堆中的字符串对象,实际上是在堆上new出了个StringBuilder对象来进行append操作再toString()**而形成的。而s1指向的是常量池中的对象,即返回false。
- 同上,即返回false。
- 用final修饰的变量,编译器将其看成常量,编译器已知,直接用"Master"代替s3等效于"Andy" + “Master”。,即返回true。
- 创建了两个不同的StringBuilder对象,即返回false。
VII.字面量和常量池初探
字符串对象内部是用字符数组存储的,那么看下面的例子:
String m = "hello,world";
String n = "hello,world";
String u = new String(m);
String v = new String("hello,world");
- 会分配一个11长度的char数组,并在常量池分配一个由这个char数组组成的字符串,然后由m去引用这个字符串
- 用n去引用常量池里边的字符串,所以和n引用的是同一个对象
- 生成一个新的字符串,但内部的字符数组引用着m内部的字符数组
- 同样会生成一个新的字符串,但内部的字符数组引用常量池里边的字符串内部的字符数组,意思是和u是同样的字符数组
String m = "hello,world";
String n = "hello,world";
String u = new String(m);
String v = new String("hello,world");
System.out.println(m == n);
System.out.println(m == u);
System.out.println(m == v);
System.out.println(u == v);
答:
true
false
false
false
- m和n指向都是常量池中的对象
- m,u,v都是不同的对象
- m,u,v,n都使用了同样的字符数组,并且用equal判断的话也会返回true
参考:
https://segmentfault.com/a/1190000009888357
https://blog.csdn.net/xdugucc/article/details/78193805