细谈String对象的比较判断

细谈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;

但笔者会以新角度来讲解字符串的比较判断。




笔者在讲解法则之前,想先阐述一些混乱的概念

  1. 代码中所有以 "" 包裹的字符串被称为常量,这些字符串在编译时直接存放于CLASS文件的文件常量池。可以通过命令javap -v class文件来查看相关信息。当这些class文件被加载后,会存放于字符串常量池。
  2. 代码中所有以String引用直接指向上述常量的,都被称为变量(即String str="STR")。该以final修饰该String引用,则仍被被称为常量(即final String str="STR")。
  3. 代码中所有以String引用指向堆对象的,无论是否有final修饰,都被称为变量
    • 且在与其它内容值相等的字符串对象进行直接比较判断时永远false(因为该引用指向的是堆对象)
    • String str=new String("内容值")其中“内容值”算是一个常量!编译并被加载后,会被存放于字符串常量池。所以才会有一种说法:通过new String()创建出来的堆对象会被栈中引用所指,同时自身也会指向字符串常量池——这种说法是错误的,相信大家应该明白了
    • String str=new String("内")+new String("容")+new String("值"):此时str作为栈中引用,指向堆中对象,但堆对象不再指向常量池

好了,现在可以谈谈笔者所定义的法则了。

  1. 当+号一边存在字符串变量时,则会在堆中创建一个内容为该拼接字符串的对象。
  2. 当+号两边都是字符串常量时,则先会寻找字符串常量池中是否存在已经拼接好的字符串。
    • 如果不存在,则会在池中创建一个新的字符串,不会在堆中创建新的对象。
    • 最后将字符串在常量池中的地址赋给=左边的字符串变量。
  3. this指针导致JVM的静态常量传播优化失效 。
    • 由于字符串的特殊性(可能是因为其不可变),所以final String str="STR"即算为静态常量(若有static修饰,则更是一个静态常量) 。
    • 当使用this指针操纵final String str="STR"时,就会被JVM认为是一个字符串变量,然后按上述规则进行拼接运算("THIS IS A "+this.str)。
  4. 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);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值