String中的常见错误“==”

String中“==”错误

前段时间复习了数据类型的相关内容。众所周知,数据类型分为基本数据类型和对象数据类型。基本数据类型的使用在C语言的学习中已经较为系统的学习了;而在学习java面向对象编程之后,对象数据类型才开始正式进入视野。
对象类型相比于基本类型,不仅有值还有ID,在堆中而不是市在栈中分配内存,使用代价相对较高,但是功能远远比基本类型要丰富。
在Java所给出的对象类型中,String是常用类型中较为少见的不可变类型。因此,对于String的有些操作,很多情况下和直觉相矛盾。
例如最常用的“==”操作。

String生成方式

String的生成有两种方式。一种是仿照基本数据类型,直接使用“=”来进行生成,例如“String s=“内容””这种方式在形式上来看更接近于赋值等操作,也是最常用的方式;另一种是和其他对象数据类型相似,使用构造器语句“String s=new String(“内容”)”的方式,这种方式较为规范,但是使用频率的反而较低。
两种方式实质上的区别在于是否生成了对象。对于第二种方式而言,Java在执行该语句的时候,由于明确地使用了构造器的方法,因此该方式明确地生成了一个String的实例对象。
而对于第一种方式而言,这里就要牵扯到常量池这一概念。

String常量池

接上面所说,简单地学习了java语言中String常量池的概念。
java语言的执行在java虚拟机jvm上完成。对于jvm,虚拟内存分布主要包括程序计数器、本地方法栈、虚拟机栈、方法区和虚拟机堆,其中前三者为线程私有,后两者则是所有线程共享。对于方法区而言,方法区为静态区,负责存储加载的类、静态变量、静态方法、常量以及成员方法,其中运行时常量池包含了类的运行时常量和静态方法等Class常量池的数据。
可以看到,方法区中有一个单独的常量池,负责存储运行时所需的常量,而其中就包含了String常量池。
在Java虚拟机运行的时候,jvm会将在程序中所用到的类中的常量装载到运行时常量池中,以便于后续的使用。而对于String类,java在构建String类的时候,实际上在String类的内部已经构建了静态常量池(存在于.class文件中),包含常用字符串常量,运行时该部分直接装载,编译后,对于某些String常量可以直接在该常量池中寻找到值,并在构建String对象的时候直接指向该常量完成该过程。
这也可以解释,为什么String作为一个对象类型,在使用构建两个相同值的String的时候,会出现使用“==”和“.equals”方法判断时均会出现相等的情况。例如:

// 例子,该方法最后打印出两个true
String s1="a";
String s2="a";
System.out.println(s1==s2);
System.out.println(s1.equals(s2));

那么这里就产生了一个问题。
对于用户而言,我很有可能随意地构造字符串String;而对String类的创建者而言,其能够收录的字符串是有限的。但是对于我构建的任何一个字符串,执行上述例子中的相应过程,得到的答案都是两个true,这是为什么?
原因在于,在编译期间,代码中的字面量在第一次被发现之后,会直接放入class文件的常量池中,从而实现复用,载入运行时常量池后,两个对象指向同一地址,因此会相等。

连接字符串时的拼接操作

这里给出一个例子:

// 一个例子
String s1="abc";
String s2="a"+"bc";
System.out.println(s1==s2);
System.out.println(s1.equals(s2));

在这个例子中,最终得到的结果依然是两个true。理论上来说,第二个字符串应该是两个字符串拼接完成的,两者不相等。但是,对于java编译器而言,由于两个拼接部分均为已知字面量,因此在编译期间,这种拼接会被优化,编译器直接帮你拼好,“String 时=“a”+“bc”;”实际上等价于“String s1=“abc”;”。因此两者相等。

另一个例子:

// 一个例子
String s1="abc";
String a="a";
String bc="bc";
String s2=a+bc;
System.out.println(s1==s2);
System.out.println(s1.equals(s2));

这里第一个打印结果就出现了false,为什么?
通过查阅和思考,最后发现。因为s2在够贱的时候实际上已经和常量池没有直接联系了,构建的过程是将String对象a和String对象bc两者连接起来构建昵称一个新的对象,在构建的过程中s2只连接到a和bc,由a和bc去常量池中寻找引用的字符串常量“a”和“bc”,而后在堆中构造该字符串,并令s2完成指向引用。

因此,我们可以发现,单步直接联系到常量池的操作,一般构建出的字符串使用“==”和“.equals”进行判断的时候两者均可以得到true结果,而一旦经过了中转或者说“拐弯”,在该过程中涉及到了堆有关的操作,则使用“= =”进行判断的时候便会出现不相等(引用已经发生了差异)。

写在后面

通过查阅资料,了解到第二种:对于“String s=new String(“内容”)”的方法,实际上构建了两个字符串:一是在编译过程中,由于变化与读取到了内容从而在常量池中创建了一个内容常量,该部分由classloader加载执行;二是在调用过程中,该语句在堆中创建一个内容对象,该部分在执行阶段完成,由编译阶段中载入常量池的内容常量拷贝到堆中完成创建。
另外,对于String对象而言,在构造时前面加上final,会使该String变量变为常量对象(由于加上了final导致failed变量不可更改),因此若代码为:

// 一个例子
	String s1="abc";
	final String a="a";
	final String bc="bc";
	String s2=a+bc;
	System.out.println(s1==s2);
	System.out.println(s1.equals(s2));

则输出结果两个都为true。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值