new String、声明String、String变量常量相加的区别详解
1、new String和声明String区别和实现过程
1)String str1 = "abcd"的实现过程:首先栈区创建str引用,然后在String池(独立于栈和堆而存在,存储不可变量)中寻找其指向的内容为"abcd"的对象,如果String池中没有,则创建一个,然后str指向String池中的对象,如果有,则直接将str1指向"abcd";
如果后来又定义了字符串变量 str2 = “abcd”,则直接将str2引用指向String池中已经存在的“abcd”,不再重新创建对象;当str1进行了赋值(str1=“abc”),则str1将不再指向"abcd",而是重新指String池中的"abc",此时如果定义String str3 = “abc”,进行str1 == str3操作,返回值为true,因为他们的值一样,地址一样,但是如果内容为"abc"的str1进行了字符串的+连接str1 = str1+“d”;此时str1指向的是在堆中新建的内容为"abcd"的对象,即此时进行str1==str2,返回值false,因为地址不一样。
2)String str3 = new String(“abcd”)的实现过程:先去常量池中查找有没有这个字符串,没有的话在常量池中创建,并在堆中也创建。如果常量池中有这个字符串就不会在常量池中创建,但依然还是会在堆中创建。返回的都是是堆中分配的空间。如果后来又有String str4 = new String(“abcd”),str4不会指向之前的对象,而是重新创建一个对象并指向它,所以如果此时进行str3==str4返回值是false,因为两个对象的地址不一样,如果是str3.equals(str4),返回true,因为内容相同。因此,new出来的永远指向堆中的空间,和声明出来的==的结果肯定是false
从编译角度分析两者区别:
String str1 = “abcd”: 在编译期,JVM会去常量池来查找是否存在“abcd”,如果不存在,就在常量池中开辟一个空间来存储“abcd”;如果存在,就不用新开辟空间。然后在栈内存中开辟一个名字为str1的空间,来存储“abcd”在常量池中的地址值。
String str3 = new String(“abcd”): 在编译阶段JVM先去常量池中查找是否存在“abcd”,如果过不存在,则在常量池中开辟一个空间存储“abcd”。在运行时期,通过String类的构造器在堆内存中new了一个空间,然后将String池中的“abcd”复制一份存放到该堆空间中,在栈中开辟名字为str2的空间,存放堆中new出来的这个String对象的地址值。 内存图: 说明:
首先,通过main()方法进栈。
然后再栈中定义一个对象s1,去堆中开辟一个内存空间,将内存空间的引用赋值给s1,“hello”是常量,然后去字符串常量池 查看是否有hello字符串对象,没有的话分配一个空间存放hello,并且将其空间地址存入堆中new出来的空间中。
在栈中定义一个对象s2,然后去字符串常量池中查看是否有”hello”字符串对象,有,直接把”hello”的地址赋值给s2.
即s1中存的是堆中分配的空间,堆中分配的空间中存的是字符串常量池中分配空间存放”hello”的空间的地址值。而s2中之间存的是字符串常量池中分配空间存放”hello”的空间的地址值。
由于s1与s2中存放的地址不同,所以输出false。因为,类String重写了equals()方法,它比较的是引用类型的 的值是否相等,所以输出true。即结果为false、true。
验证:
String s1=new String("xyz");
String s2="xyz";
Boolean a1 = s1.equals(s2);
Boolean a2=(s1==s2);
System.out.println(a1+" "+a2);//true false
String b1="qwe";
String b2 = new String("qwe");
String b3 = b2.intern();
Boolean c1=b1.equals(b2);
Boolean c2=(b1==b2);
Boolean c3=(b1==b3);
System.out.println(c1+" "+c2+" "+c3);//true false true
c3是true说明:b2(堆中分配的空间)中存放的字符串常量池中分配空间存放“qwe“的空间的地址值(b3)与b1中存放的字符串常量池中分配空间存放”qwe“的地址一致。
即new之前会在常量池中查找是否存在这个字符串,如果存在,就直接把常量池中的地址值存放在新建的堆空间对象中。
inter方法:
JDK1.6中的intern: 调用intern方法的时候首先会去常量池中查看是否存在与当前String值相同的值,如果存在的话,则直接返回常量池中这个String值的引用;如果不存在的话,则会将原先堆中的该字符串拷贝一份到常量池中。
JDK1.7中的intern: 调用intern方法的时候首先会去常量池中查看是否存在与当前String值相同的值,如果存在的话,则直接返回常量池中这个String值的引用;如果不存在的话,则只会将原先堆中该字符串的引用放置在常量池中,并不会将拷贝整个字符串到常量池中。 这也就说明,JDK1.6和JDK1.7对于常量池中不存在此字符串的情况处理不同。
2、拼接、相加问题:
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
System.out.println(s3 == s1 + s2);// false
System.out.println(s3.equals((s1 + s2)));// true
System.out.println(s3 == "hello" + "world");//true
System.out.println(s3.equals("hello" + "world"));// true
System.out.println(s3 == s1 + "world");//false
代码详解:
- equals()比较方法不解释,比较值,均相等,均为true。
- s1与s2相加是先在字符串常量池中开一个空间,然后拼接,这个空间的地址就是s1与s2拼接后的地址。与s3的地址不同,所以输出为false。
- s3与”hello”+”world”作比较,”hello”+”world”先拼接成“helloworld”,然后再去字符串常量池中找是否有”helloworld”,有,所以和s3共用一个字符串对象,则为true。
- 如果拼接的一个是变量一个是常量,依然会在常量池中开一个新的空间,然后拼接,即只要拼接中有变量都会开辟一个新的空间,所以s3 == s1 + "world"是false
3、总结
- String s = new String(“hello”)会创建2(1)个对象,String s = “hello”创建1(0)个对象。
注:当字符串常量池中有对象hello时括号内成立! - “hello”创建1(0)个对象。
注:当字符串常量池中有对象hello时括号内成立! - 字符串如果是变量相加(只要有变量),先开空间,再拼接。
- 字符串如果是常量相加,是先加,然后在常量池找,如果有就直接返回,否则,就创建。(常量相加是在编译期就完成)