首先我们需要了解java字符串池的概念,说到字符串池,我们不得不提出另一个概念,string interning,可以翻译为字符串驻留,字符串驻留是一种每一个不同的字符串值只有一份存储的方法。
我们来看一看intern方法
public String intern() 返回字符串对象的规范化表示形式。
一个初始时为空的字符串池,它由类 String 私有地维护。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。 它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。对于任意两个字符串s和t,当且仅当s.equals(t)为true时,s.intern() == t.intern()才为true。所有字面值字符串和字符串赋值常量表达式都使用 intern方法进行操作。
来看代码片段:
public class Test {
public static void main(String args[]) {
String a="1234";
String b=new String("1234");
String s=new String("1234");
String c=a;
System.out.println(a==b);
System.out.println(a==c);
System.out.println(a==s);
System.out.println(b==s);
System.out.println(a.equals(b));
System.out.println(a.equals(c));
System.out.println(a.equals(s));
System.out.println(a.intern()==b.intern());
System.out.println(a.intern()==s.intern());
System.out.println(a.intern()==c.intern());
}
}
结果:
false true false false true true true true true true
其实我们无需比较他们的内容,他们的内容都是“1234”,如果是使用new String()来创建一个字符串,虽然他们的字符内容相同,但是在内存中所占地址不同。
我们继续看到几个字符串之间使用intern()函数进行比较的结果,发现都是相等的。根据intern()函数的定义,调用这个函数的时候,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。通过a和b之间intern()函数返回值的比较,发现虽然这两个变量在内存中的地址不同,但是他们的intern()返回值是相同的,为什么会这样呢?因为通过new创建的字符串并不会存放在字符串池中,而是存放在堆内存中,什么是堆内存呢?Java的内存被划分为栈内存和堆内存,在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配,堆内存用来存放由new创建的对象和数组。所以说,内容相同的字符串不会同时存在于字符串池中。
那么现在,我们重回字符串池。在java中,字符串池其实是一个常量池,所有字符串被实例化之后,直到被回收,它是不会改变的。
我们再给出一个例子,这个例子来自stackoverflow。
package testPackage;
class Test {
public static void main(String[] args) {
String hello = "Hello", lo = "lo";
System.out.print((hello == "Hello") + " ");
System.out.print((Other.hello == hello) + " ");
System.out.print((other.Other.hello == hello) + " ");
System.out.print((hello == ("Hel"+"lo")) + " ");
System.out.print((hello == ("Hel"+lo)) + " ");
System.out.println(hello == ("Hel"+lo).intern());
}
}
class Other { static String hello = "Hello"; }
package other;
public class Other { public static String hello = "Hello"; }
结果:
true true true true false true
我们来逐个分析比较结果:
1) 第一个的比较可以写成这样
String h=hello;
System.out.println(hello == h);
因为并未创建新的实例,所以这个相等。
2) 第二个比较
我们将第二个和第三个一起来说,不论是包内还是包外的静态字符串变量,他们也都需要在同个字符串池中实例化,按照程序的执行顺序,第一个被实例化的字符串是hello,于是字符串中存在了一个”Hello”。当程序执行到Other.hello == hello,这时需要调用Other的静态变量hello,在这时会先将Other这个类加载,加载类的时候,会初始化他的静态变量,这时并不会创建新的变量,只是将字符串池中的”Hello”的引用给了Other里的静态变量hello,如果用代码来展示这个过程,我们可以这样:
class Other{
public static String hello="Hello".intern();
}
这样的内部实现只是我的猜测,意思就是这样。
第三个的解释与第二个大致相同,这里不多解释。
4) 第四个比较
第四个比较和第五个比较的内容看起来相似,比较的结果却相反,我们将这两个一起分析一下。
第四个比较的右边是(“Hel”+”lo”),这个在编译的时候就会执行,将这两个字符串变成一个字符串,其实比较就变成了这样hello == (“Hello”),转化过后结果就很明显了。
第五个比较的右边是(“Hel”+lo),在编译的时候并不会执行,直到运行的时候,才会加载lo变量的值。先举个例子:
String as="java";
String ss="ja";
ss=ss+"va";
System.out.println(as==ss);
通过这个例子我们可以看出,当一个字符串变量和一个字符串常量相加的时候,并不会像第二个比较和第三个比较那样使用intern()函数来返回实例,而是使用new String()来创建一个新的实例。所以他们的地址是不同的,比较的结果就不会相等。
6) 第六个比较
虽然(“Hel”+lo)创建了新的字符串,但是这个字符串调用了intern方法,在常量池中找到了”Hello”这个字符串,所以返回了这个字符串的实例。比较的值当然相等。
这里的讨论应该结束了,但是我又发现了一个有趣的问题,来自知乎:
这个问题我并不懂,看了回答似乎了解了一些,很有意思的问题。