细谈String对象的比较判断
本文不探讨String的编码方式
太多文章都会这样讲:
- 由于str1和str2所指向的内存地址不同,所以会导致直接判断时,两个内容值一样的字符串可能并不相同。
- intern():若在池中找不到该字符串(以equals判断是否相当),则将该对象加入池中,并返回该字符串在字符串常量池的地址。
String str1="this is a string"; // 栈中引用指向字符串常量池
String str2=new String("this is a string"); // 栈中引用指向堆对象,堆对象指向字符串常量池的一个字符串
System.out.println(str1==str2); // false;
但笔者会以新角度来讲解字符串的比较判断。
笔者在讲解法则之前,想先阐述一些混乱的概念
- 代码中所有以
""
包裹的字符串被称为常量,这些字符串在编译时直接存放于CLASS文件的文件常量池。可以通过命令javap -v class文件
来查看相关信息。当这些class文件被加载后,会存放于字符串常量池。 - 代码中所有以String引用直接指向上述常量的,都被称为变量(即
String str="STR"
)。该以final
修饰该String引用,则仍被被称为常量(即final String str="STR"
)。 - 代码中所有以String引用指向堆对象的,无论是否有
final
修饰,都被称为变量。- 且在与其它内容值相等的字符串对象进行直接比较判断时永远
false
(因为该引用指向的是堆对象) String str=new String("内容值")
,其中“内容值”算是一个常量!编译并被加载后,会被存放于字符串常量池。所以才会有一种说法:通过new String()
创建出来的堆对象会被栈中引用所指,同时自身也会指向字符串常量池——这种说法是错误的,相信大家应该明白了!String str=new String("内")+new String("容")+new String("值")
:此时str作为栈中引用,指向堆中对象,但堆对象不再指向常量池!
- 且在与其它内容值相等的字符串对象进行直接比较判断时永远
好了,现在可以谈谈笔者所定义的法则了。
- 当+号一边存在字符串变量时,则会在堆中创建一个内容为该拼接字符串的对象。
- 当+号两边都是字符串常量时,则先会寻找字符串常量池中是否存在已经拼接好的字符串。
- 如果不存在,则会在池中创建一个新的字符串,不会在堆中创建新的对象。
- 最后将字符串在常量池中的地址赋给
=
左边的字符串变量。
this
指针导致JVM
的静态常量传播优化失效 。- 由于字符串的特殊性(可能是因为其不可变),所以
final String str="STR"
即算为静态常量(若有static
修饰,则更是一个静态常量) 。 - 当使用
this
指针操纵final String str="STR"
时,就会被JVM
认为是一个字符串变量,然后按上述规则进行拼接运算("THIS IS A "+this.str
)。
- 由于字符串的特殊性(可能是因为其不可变),所以
intern()
:- 返回该字符串内容值在字符串常量池中的地址。
- 若字符串常量池中不含该内容值,则向常量池注入该内容值,并使该字符串引用指向内容值在常量池中的地址(仅当字符串常量池中不含该内容值时,才会自动修改引用)。
TALK IS CHEAP, SHOW ME CODE
public class DemoStr
{
private final String str = "a";
public static void main(String[] args)
{
String target = "ab";
String ab1 = str + "b"; // 法则2
String ab2 = this.str + "b"; // 法则3
String a = "a";
String b = "b";
System.out.println(ab1 == target);
System.out.println(ab2 == target);
System.out.println(a + b == target); // 法则1(‘+’号两边都是变量)
}
}
true
false
false
接下来就是大名鼎鼎的intern()
了
笔者的有些概念和法则,可能与其他笔者观点不同(如:intern会在某些情况下修改栈中引用的指向)。但笔者的所有概念与法则均可支持这些实验数据,而且其他笔者的某些概念也不见得完全正确(如:通过
new String()
创建出来的堆对象会被栈中引用所指,同时自身也会指向字符串常量池——这其实是因为JVM会把代码中直接以""
所包裹的字符串纳入字符串常量池,而new String()
时往往会使用""
来包裹字符串)
public class DemoStr
{
public static void main(String[] args)
{
String s1 = new String("he") + new String("llo");// 根据概念1,虽然s1指向堆对象,但堆对象没有与常量池所关联
String s2 = new String("h") + new String("ello");// 此时常量池并没有“hello”
String s3 = s1.intern(); // 根据法则4,s1调用该方法后,常量池被注入“hello”,并将s1指向常量池中“hello”的地址,最后返回常量池中“hello”的地址
String s4 = s2.intern(); // 根据法则4,s2调用该方法时,常量池已存在“hello”,所以直接返回“hello”的地址
System.out.println(s1 == s3);
System.out.println(s1 == s4);// 根据法则4,此时s1已经指向常量池中“hello”的地址,所以必然相等
System.out.println(s2 == s3);
System.out.println(s2 == s4);// 根据法则4,此时s2还是指向堆对象,所以必然不等
}
}
true
true
false
false
public class DemoStr
{
public static void main(String[] args)
{
String str1 = new String("this is a") + new String(" ") + new String("string");
String str2 = "this is a string"; // 根据概念1,此时JVM向常量池中注入该字符串
str1.intern(); // 根据法则4,此时直接返回该字符串在常量池的地址
System.out.println(str2 == str1); // false
System.out.println(str2 == str1.intern()); // true
System.out.println(str1);
str1 = new String("hello") + new String(",") + new String("world");
str1.intern(); // 根据法则4,常量池被注入该字符串,并将s1指向常量池中该字符串的地址,最后返回常量池中该字符串的地址
str2 = "hello,world"; // 根据概念1,JVM会向str2返回该字符串在常量池中的地址
System.out.println(str2 == str1); // true
System.out.println(str2 == str1.intern()); // true
System.out.println(str2);
str1 = new String("love"); // 根据概念1,此时已向常量池注入该字符串
str1.intern(); // 根据法则4,此时直接返回该字符串在常量池的地址
str2 = "love";
System.out.println(str2 == str1); // false
System.out.println(str2 == str1.intern()); // true
System.out.println(str1);
// 最后几个示例的结果交给读者判断
String temp1 = "q", temp2 = "w", temp3 = "e";
str1 = temp1 + temp2 + temp3; // 法则1
str2 = "qwe";
str1.intern(); // 法则4,此时直接返回该字符串在常量池中的地址
System.out.println(str2 == str1);
System.out.println(str2 == str1.intern());
System.out.println(str2);
temp1 = "j";temp2 = "k";temp3 = "l";
str1 = temp1 + temp2 + temp3; // 法则1
str1.intern(); // 根据法则4,常量池被注入该字符串,并将s1指向常量池中该字符串的地址,最后返回常量池中该字符串的地址
str2 = "jkl";
System.out.println(str2 == str1);
System.out.println(str2 == str1.intern());
System.out.println(str1);
}
}