在JDK6.0及之前版本,字符串常量池存放在方法区中。而在JDK7.0版本以后,字符串常量池被移到了堆中。
注意:只要使用 new String() 方法,便会在堆中创建新的字符串对象!
== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否指向同一个对象。
注意:
equals() 用来比较两个对象是否相等,由于所有的类都是继承自java.lang.Object类的,在Object中的基类中定义了一个equals方法,这个方法的初始行为是比较对象的内存地址,但String类中重写了equals方法, 比较的是字符串的内容 ,而不再是比较类在堆内存中的存放地址了,即两个字符串的内容相同则返回true,反之返回false。
翻译翻译:
String类的intern方法():一个初始为空的字符串池,它由String类独自维护。
当调用 intern方法时,如果池中已经包含一个等于此String对象的字符串(用equals(Object)方法确定),则返回池中的字符串。
否则,将此String对象添加到池中,并返回此String对象的引用。
对于任意两个字符串s和t,当且仅当s.equals(t)为true时,s.intern() == t.intern()才为true。
所有字面值字符串和字符串赋值常量表达式都使用 intern 方法进行操作。
String s = "abc";
String s2 = "abc";
System.out.println(s == s2);//true
s与s2引用的都是字符串常量池中的"abc",结果为true。
String s = "abc";//在串池中创建"abc"
String s2 = new String("abc");//在堆中创建"abc"
String s3 = new String("abc");//在堆中创建另一个"abc"
System.out.println(s == s2);//false
System.out.println(s2 == s3);//false
System.out.println(s.equals(s2));//true
System.out.println(s2.equals(s3));//true
s2指向堆中创建的一个"abc"字符串对象,而s3指向堆中创建的另一个"abc"字符串对象。s2与s3是两个指向不同对象的引用,结果为false。
String s = "aabb";//在串池中创建"aabb"
String s1 = "aa" + "bb";
String s2 = "a" + "abb";
System.out.println(s == s1);//true
System.out.println(s1 == s2);//true
在常量池中创建"aa"、"bb"、"a"、"abb",连接过程编译器直接优化成"aabb",而串池中已经有了"aabb",所以s1和s2引用的都是字符串常量池中的"aabb"。
注意:字符串常量池中有"aabb"、"aa"、"bb"、"a"、"abb"。
String s = "aabb";//在串池中创建"aabb"
String s1 = "aa";//在串池中创建"aa"
String s2 = s1 + "bb";//会在堆中产生一个新的"aabb"对象
System.out.println(s == s2);//false
System.out.println(s.equals(s2));//true
拼接字符串涉及到变量(不全为常量,即使“运算数”s1是用双引号创建的常量)的相加时,会在堆中创建一个新的对象,因为s1在编译之前是未知的,其内部实现是先new一个StringBuilder,然后 append(s1).append("bb");最后s2引用toString()返回的对象。即s2为堆中"aabb"字符串对象的引用。
String s1 = "aa";//在串池中创建"aa"
String s2 = "bb";//在串池中创建"bb"
String s3 = "aabb";//在串池中创建"aabb"
final String str1 = "aa";
final String str2 = "bb";
System.out.println("aa" + "bb" == s3);//true
System.out.println(s1 + s2 == s3);//false
System.out.println(str1 + str2 == s3);//true
解答:
"aa" + "bb" == s3;拼接过程编译器直接优化成"aabb",而串池中已经有了"aabb",则直接指向串池中的"aabb",且s3为串池中"aabb"字符串常量的引用,所以结果为true。
s1 + s2 == s3;有变量引用的字符串是不能优化的,除非变量是final修饰的或者直接为"aa" + "bb"的常量形式拼接,s1 + s2会在堆中创建一个新的"aabb"字符串对象,但s3为串池中"aabb"字符串常量的引用,所以结果为false。
final修饰的为常量,所以str1 + str2为常量拼接,str1 + str2 == s3;等同于"aa" + "bb" == s3;所以结果为true。
①例一
String s = "aabb";
System.out.println(s == s.intern());//true
解答:
在串池中创建"aabb",执行s.intern()发现串池中已经有"aabb",则返回池中的字符串"aabb"。执行结果为true。
②例二
String s = "aa" + "bb";//连接过程编译器直接优化成"aabb",串池中没有"aabb",则在串池中创建"aabb"
System.out.println(s == s.intern());//true
解答:
常量拼接,拼接后s为"aabb",因为串池中没有"aabb",则就在串池中创建"aabb";执行s.intern()发现串池中已经有"aabb",则返回池中的字符串"aabb"。即s和s.intern()引用的都是字符串常量池中的"aabb",则执行结果为true。
③例三
String s = new String("aa") + new String("bb");
System.out.println(s == s.intern());//true
解答:
new String("aa")先在字符串常量池中创建一个"aa",再在堆中创建一个"aa"字符串对象,new String("bb")先在字符串常量池中创建一个"bb",再在堆中创建一个"bb"字符串对象,拼接后在堆中产生一个新的"aabb"字符串对象,s为堆中"aabb"字符串对象的引用。串池中有:"aa"、"bb",堆中有:"aa"、"bb"、"aabb"。
执行s.intern(),发现串池中没有"aabb",则将"aabb"添加到池中,并返回此String对象即堆中"aabb"字符串对象的引用,则s.intern()返回的是堆中"aabb"字符串对象的引用。
s和s.intern()都是堆中"aabb"字符串对象的引用,则执行结果为true。
④例四
String s = "a" + new String("a") + "bb";
System.out.println(s == s.intern());//true
解答:
在字符串常量池中创建"a";new String("a")先在字符串常量池中创建一个"a",再在堆中创建一个"a"字符串对象;在字符串常量池中创建"bb"。相当于String s = new StringBuilder("a").append("a").append("bb").toString();拼接后在堆中产生一个新的"aabb"字符串对象,则s为堆中"aabb"字符串对象的引用。串池中有:"a"、"bb",堆中有:"a"、"aabb"。
执行s.intern(),发现串池中没有"aabb",则将"aabb"添加到池中,并返回此String对象即堆中"aabb"字符串对象的引用,则s.intern()返回的是堆中"aabb"字符串对象的引用。
s和s.intern()都是堆中"aabb"字符串对象的引用,则执行结果为true。
⑤例五
String s = new String("aabb");
System.out.println(s == s.intern());//false
解答:
new String("aabb")先在字符串常量池中创建一个"aabb",再在堆中创建一个"aabb"字符串对象,然后将堆中的这个"aabb"对象引用给s,即s为堆中"aabb"字符串对象的引用。串池中有:"aabb",堆中有:"aabb"。
执行s.intern(),发现串池中已经有"aabb",则直接返回池中的字符串。即s.intern()返回的是串池中"aabb"的引用。
s为堆中"aabb"字符串对象的引用,s.intern()是串池中"aabb"的引用,则执行结果为false。
①例一
String s1 = new String("go") + new String("od");//s1为 堆中"good"字符串对象的引用
String s2 = s1.intern();//串池中没有"good",则将"good"添加到池中,且常量池中保存的是堆中"good"对象的引用,再返回此String对象,即 s2为堆中"good"字符串对象的引用
String s3 = "good";
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//true
System.out.println(s2 == s3);//true
解答:
第一行,new String("go")先在串池中创建创建一个"go",再在堆中创建一个"go"字符串对象,new String("od")先在串池中创建创建一个"od",再在堆中创建一个"od"字符串对象,拼接后在堆中创建了一个新的"good"字符串对象,则s1为堆中"good"字符串对象的引用。串池中有:"go"、"od",堆中有:"go"、"od"、"good"。
第二行,String s2 = s1.intern();发现串池中没有"good",则将"good"添加到池中(常量池中保存的是堆中"good"的引用),再返回此String对象,即s2为堆中"good"字符串对象的引用。
第三行,String s3 = "good";因为串池中已经有一个引用保存了"good"(jdk1.8字符串常量池允许将引用地址作为对象去存储),s3就直接指向这个地址,即s3还是引用的堆中的"good"。
s1、s2、s3都是堆中"good"的引用。则s1 == s2、s1 == s3、s2 == s3的结果都为true。
②例二
String s1 = new String("go") + new String("od");//s1为 堆中"good"字符串对象的引用
String s2 = "good";//串池中没有“good”,则将“good”添加到池中,然后s2引用“good”,即 s2为串池中“good”的引用
String s3 = s1.intern();//串池中已经有“good”,则返回池中的字符串,即 s3为串池中“good”的引用
System.out.println(s1 == s2);//false
System.out.println(s1 == s3);//false
System.out.println(s2 == s3);//true
解答:
第一行,new String("go")先在串池中创建创建一个"go",再在堆中创建一个"go"字符串对象,new String("od")先在串池中创建创建一个"od",再在堆中创建一个"od"字符串对象,拼接后在堆中创建了一个新的"good"字符串对象,则s1为堆中"good"字符串对象的引用。串池中有:"go"、"od",堆中有:"go"、"od"、"good"。
第二行,String s2 = "good";串池中没有"good",则在串池中创建"good",并让s2引用串池中的"good"字符串常量。
第三行,String s3 = s1.intern();发现串池中已经存在"good",则返回池中的字符串,则s3为串池中"good"字符串常量的引用。
s1为堆中"good"字符串对象的引用,s2和s3为串池中"good"字符串常量的引用。则s1 == s2和s1 == s3的结果为false,s2 == s3的结果为true。
③例三
String s1 = new String("g") ;
String s2 = new String("ood") ;
String s3 = s1 + s2;//s3为堆中"good"字符串对象的引用
String s4 = s3.intern();//串池中没有“good”,则将“good”添加到池中,再返回此String对象,即s4为堆中"good"字符串对象的引用
String s5 = "good";
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//true
System.out.println(s4 == s5);//true
解答:
第一行,new String("go")先在串池中创建创建一个"go",再在堆中创建一个"go"字符串对象。s1为堆中"go"字符串对象的引用。
第二行,new String("od")先在串池中创建创建一个"od",再在堆中创建一个"od"字符串对象。s2为堆中"od"字符串对象的引用。
执行第一行和第二行后,串池中有:"g"、"ood",堆中有:"g"、"ood"、"good"。
第三行,String s3 = s1 + s2; s1与s2拼接后在堆中产生一个新的"aabb"字符串对象,则s3为堆中"good"字符串对象的引用。
第四行,String s4 = s3.intern();发现串池中没有"good",则将"good"添加到池中(常量池中保存的是堆中"good"的引用),再返回此String对象,即s4为堆中"good"字符串对象的引用。
第五行,String s5 = "good";因为串池中已经有一个引用保存了"good"(jdk1.8字符串常量池允许将引用地址作为对象去存储),s5就直接指向这个地址,即s5还是引用的堆中的"good"。
s3、s4、s5都是堆中"good"的引用。则s3 == s4、s3 == s5、s4 == s5的结果都为true。
④例四
String s1 = new String("g");
String s2 = new String("ood");
String s3 = s1 + s2;//s3为堆中"good"字符串对象的引用
String s4 = "good";//串池中没有“good”,则将“good”添加到池中,然后s4引用“good”,即 s4为串池中“good”的引用
String s5 = s3.intern();//串池中已经有“good”,则返回池中的字符串,即 s5为串池中“good”的引用
System.out.println(s3 == s4);//false
System.out.println(s3 == s5);//false
System.out.println(s4 == s5);//true
解答:
第一行,new String("go")先在串池中创建创建一个"g",再在堆中创建一个"g"字符串对象。s1为堆中"g"字符串对象的引用。
第二行,new String("od")先在串池中创建创建一个"opd",再在堆中创建一个"ood"字符串对象。s2为堆中"ood"字符串对象的引用。
执行第一行和第二行后,串池中有:"g"、"ood",堆中有:"g"、"ood"、"good"。
第三行,String s3 = s1 + s2; s1与s2拼接后在堆中产生一个新的"aabb"字符串对象,则s3为堆中"good"字符串对象的引用。
第四行,String s4 = "good";串池中没有"good",则在串池中创建"good",并让s4引用串池中的"good"字符串常量。
第五行,String s5 = s3.intern();发现串池中已经存在"good",则返回池中的字符串,则s5为串池中"good"字符串常量的引用。
s3为堆中"good"字符串对象的引用,s4和s5为串池中"good"字符串常量的引用。则s3 == s4和s3 == s5的结果为false,s4 == s5的结果为true。
文中如有不正确的地方,请多多指教互相学习,谢谢!