java的String创建细节
本文主要讲述String创建的内存分布
示例代码如下:
1 public class StringExercise { 2 public static void main(String[] args) { 3 // 创建方式不止这两种 4 String s1 = "zwz"; 5 /** 6 * String类的成员:private final char value[]; 7 * public String(String original) { 8 * this.value = original.value; 9 * this.hash = original.hash; 10 * } 11 */ 12 String s2 = new String ("zwz"); 13 System.out.println(s1 == s2); // false 14 } 15 }
内存分布图,如下图所示:
注意:String类的成员:private final char value[],即字符串常量永远存储在常量池中,value是指向字符串常量的地址。
String s1 = "zwz";
zwz字符串存放方法区的常量池中,将其地址直接返回给s1
String s2 = new String ("zwz");
在堆中开辟存储空间,创建String类的对象,value初始化为null,zwz字符串存放方法区的常量池中,并将其地址返回给value,再将String类对象的地址返回给s2。
测试题1,代码如下:
1 public class StringExercise { 2 public static void main(String[] args) { 3 // 创建方式不止这两种 4 String s1 = "zwz"; 5 String s2 = new String ("zwz"); 6 System.out.println(s1 == s2);// false 7 // s2.intern()返回value指向的地址,即字符串在常量池中的地址。 8 System.out.println(s1 == s2.intern()); // true 9 System.out.println(s2 == s2.intern()); // false 10 } 11 }
注意:s2.intern(),返回value指向的地址,即字符串在常量池中的地址
测试题2,代码如下:
1 public class StringWork01 { 2 public static void main(String[] args) { 3 Person p1 = new Person("tom"); 4 Person p2 = new Person("tom"); 5 System.out.println(p1.getName().equals(p2.getName())); true 6 System.out.println(p1.getName() == p2.getName() ); // fasle 7 System.out.println(p1.getName() == "tom"); // fasle 8 } 9 } 10 11 class Person { 12 private String name; 13 14 public Person(String name) { 15 this.name = new String(name); 16 // this.name = name; 17 } 18 19 public String getName() { 20 return name; 21 } 22 23 public void setName(String name) { 24 this.name = name; 25 } 26 }
注意:在Person类的构造器中,如果是this.name = name,那么直接将常量池中字符串的地址返回给Person类对象的name属性;
如果是this.name = new String(name),那么将常量池中字符串的地址,返回给String类对象的value成员中,并且将该对象的地址返回给Person类对象的name属性。
测试题3,代码如下:
1 public class StringWork02 { 2 public static void main(String[] args) { 3 // public final class String 4 String s1 = "123"; 5 s1 = "345"; 6 } 7 }
问创建了几个对象?
具体创建过程如图所示:
String类是final类型,不能被继承,不能修改,因此当执行s1 = "345"后,"123"字符串并没有被销毁。
证明见如下代码:
public class StringWork02 { public static void main(String[] args) { // public final class String String s1 = "123"; System.out.println(s1.hashCode()); s1 = "345"; String s2 = "123"; System.out.println(s2.hashCode()); } }
48690 48690
运行结果来看,"123"字符串常量在常量池中的地址没有发生改变。
测试题4,代码如下:
1 public class StringWork03 { 2 public static void main(String[] args) { 3 String s = "123"+"456"; 4 5 String s1 = "123"; 6 String s2 = "456"; 7 String s3 = s1 + s2; 8 9 System.out.println(s3 == s); // false 10 } 11 }
具体·创建过程如图所示:
String s = "123"+"456";
创建时,不是创建”123“和”456“两个字符串常量,直接就是创建”123456“一个字符串常量。
String s3 = s1 + s2;
创建时,首先new StringBuilder(),创建了StringBuilder对象std,再调用append(String str)方法,依次将"123",”456“传入,组成”123456“字符串。【解释不清,请看源码】
综合测试题,代码如下:
1 public class StringWork04 { 2 String str = new String("hsp"); 3 final char[] ch = {'j','a','v','a'}; 4 public void change(String str,char[] ch){ 5 str = "java"; 6 ch[0] = 'h'; 7 } 8 public static void main(String[] args) { 9 StringWork04 sw = new StringWork04(); 10 sw.change(sw.str,sw.ch); 11 System.out.print(sw.str + " and "); 12 System.out.println(sw.ch); 13 14 } 15 }
具体内存分布情况,如下图所示:
注意① :
final char[] ch = {'j','a','v','a'}; ch[0] = 'h';
此处,ch存储的是java字符数组的地址,地址0x41自始至终没有发生变化,所以,并没有违反final的性质,因此可以修改指向【0x41】存储空间的内容。
注意② :
str = "java";
str是change()方法中的形参,传入的参数是sw.str【0x31】,str = "java",即原本指向0x31地址的str,指向了常量池地址为0x12的java字符串,但是并没有改变sw.str指向的地址【0x31】,即sw.str仍指向常量池中地址为【0x11】的hsp。
运行结果如下:
hsp and hava