java string比较时必须使用equals,这是一个定论,可是要了解这个规则确实须要很多知识。很多书上告诉你==只能比较地址,地址不同,那就是false,string存在着相同字面量不一样地址的问题。知道的多一些的,还知道有常量池,可是什么样的string在常量池呢,本文就详细的说一下各类状况。java
你们都知道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的变更,有一些代码执行的结果也就不同了。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
String sb = new StringBuilder("ja").append("va").toString();
上面java是咱们第一次写代码出现,可是常量池中早已有这个关键字,因此Intern返回的地址是早已进入常量池中的地址。
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的参数传入的(常量池),一个是做为返回值新建的(堆中)。