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

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

string常量池

        你们都知道string能经过==返回true是由于常量池,他们的地址是相同的,在两种状况下咱们会用到,第一种是编译阶段,java在编译成字节码的同时,会把常量标志出来,在编译阶段肯定的字符串会进入常量池;第二种状况就是string的intern方法,这个方法会把调用的字符串放入常量池(jdk1.7之后中会直接获取堆中string对象的引用,文章后面会具体说),而且返回常量池的地址,若是常量池有相同的字符串,则返回常量池的地址,再也不建立。app

举例展现    

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

这种状况a==b为true,由于在编译时已经肯定了xxx的字符串jvm

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对象的内容不会进入常量池。ui

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

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

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的地址是相同的。code

加号的解析

String x ="aa"+"bb";

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

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

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

下面就是用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的参数传入的(常量池),一个是做为返回值新建的(堆中)。

相关文章
相关标签/搜索