java String比较相等时必须使用equals的引申

        java string比较时必须使用equals,这是一个定论,但是要了解这个规则确实需要不少知识。不少书上告诉你==只能比较地址,地址不一样,那就是false,string存在着相同字面量不同地址的问题。知道的多一些的,还知道有常量池,但是什么样的string在常量池呢,本文就详细的说一下各种情况。

string常量池

        大家都知道string能通过==返回true是因为常量池,他们的地址是相同的,在两种情况下我们会用到,第一种是编译阶段,java在编译成字节码的同时,会把常量标志出来,在编译阶段确定的字符串会进入常量池;第二种情况就是string的intern方法,这个方法会把调用的字符串放入常量池(jdk1.7以后中会直接获取堆中string对象的引用,文章后面会具体说),并且返回常量池的地址,如果常量池有相同的字符串,则返回常量池的地址,不再创建。

举例展示    

String a = "xxx";
String b = "xxx";

这种情况a==b为true,因为在编译时已经确定了xxx的字符串

String a ="xxx1";
final int  value =1;
String b="xxx"+value;

这种情况下a==b为true,在编译时final的值已经确定,编译器进行了宏替换。

String a="xxx1";
int value =1;
String b="xxx"+value;

这种情况下a==b为flase,在编译是value作为一个变量出现,无法确定value的值,所以b对象的内容不会进入常量池。

String a ="xxx1";
final int value=getNum();// getNum()是根据各种变量计算返回1
String b="xxx"+value;

这种情况下a==b为false,此处的final 修饰的value只是一个不可变的值,在编译时期根本不知道数据是多少,此时编译不会出现宏替换的情况,b对象的内容也不会进入常量池。

String a= "xxx";
String b= new String("xxx");

这种情况下a==b为false,因为b是堆中新建的地址,a的地址在常量池,地址不同。

String a = "xxx";
String b=new String("xxx").intern();

这种情况下a==b为true,a对象的地址在常量池,新建的string虽然在堆,但是执行了intern方法,在运行时,获取到了常量池xxx的地址,并且给b,此时a和b的地址是相同的。

加号的解析

String x ="aa"+"bb";

上面情况常量池有几个String对象,猜测很多,这里就不列举了,最终只有一个对象进入常量池,就是aabb,至于aa,bb在编译时已经做了优化,并不会放入常量池中。

int value =10;
String x ="xxx"+value;

x引用的对象在哪里?堆还是栈(上面已经验证过常量池中没有),实际是在堆中。

下面就是用Javap查看的执行过程(sunjdk),String x="xxx"+value;的过程被解析成为Stringbuilder.append(“xxx”),Stringbuilder.append(value),Stringbuilder.toString(),三个步骤。

Stringbuilder.toString()就是新建了一个String对象。

jdk变动

    随着jdk的变动,有一些代码执行的结果也就不一样了。1.7开始移动常量池进入堆中了。

	          String str = new String(new char[]{'a','b'});
	          str.intern();
	          String str1 ="ab";

        如果在你熟悉API和string常量池的解析后,认为是str==str1为flase的请往下看。

        在1.7之前这个代码str和str1是不相等的,但是从1.7开始,这个str和str1地址相等了。在1.7之前,字符串常量池是在方法区中,new出来的对象在堆,调用Intern方法肯定是在字符串常量池中新建了一个对象,但是jvm为了避免字符串常量池那边oom,1.7开始把字符串常量池移入到了堆内存,在常量池没有ab字符串的时候,str.intern()方法会让常量池拿到堆里对象的引用,并没有新建一个对象,所以str1指向的对象就和str指向的对象相同了。这个可以直接在调试的时候查看对象ID来检测,或者使用hsdb来查看内存来验证。下面推荐一篇解析了intern,native代码的博客,帮助理解。http://brokendreams.iteye.com/blog/2260870

常见问题补充

1,java为什么不是第一次出现

String  sb = new StringBuilder("ja").append("va").toString();

    上面java是我们第一次写代码出现,但是常量池中早已有这个关键字,所以Intern返回的地址是早已进入常量池中的地址。

2,产生对象个数问题

String  sb = new StringBuilder("hello").toString();

    此时产生了2个hello对象,一个在常量池,一个在内存。具体请查看 StringBuilder的构造过程。已经返回对象的过程,下面是stringbuilder构造时调用的一个方法

    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

    代码的最后做了一次char[]数据拷贝。把传入的string转化成char[]保持成成员变量。然后看tostring的过程,就是新建了一个string对象

    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

    这里说的两个Hello对象,一个是作为StringBuilder的参数传入的(常量池),一个是作为返回值新建的(堆中)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值