本文主要介绍String类中的value
成员变量和intern()
方法以及==
与equals()
方法的区别。
value
成员变量
再介绍这个成员变量之前,我想先讲一下为什么Java要引入常量池这个概念。
我们指的每次创建字符串,都需要分配一段内存空间,假如说我们将相同内容的字符串包含到了程序的不同地方,那么就造成了内存浪费,这个时候自然而然我们希望每次创建一个字符串都保存起来,以后再有需要相同字符串的地方,就复用这个保存好的字符串的引用就行了。总而言之,常量池的目的就是为了节约内存。
那么value
就是用来做这件事的,它存储的就是字符串所包含的内容。当创建一个String对象时,传入的字面量的引用就作为value
的值。
这个字面量首先会去常量池里面找,找得到就返回常量池对应的字符串的引用,找不到就在常量池新建一个字符串再返回其引用。
但是value
也不总是是一个常量池中字符串的引用,比如:
String str = new String("ab") + new String("c");
对象str
的value
存的值就不会是常量池中字符串的值,因为这是两个变量相加,无法在编译时唯一确定。至于为什么说编译时无法确定就不行,请看下面这个例子:
public class MyTest {
static final String a = "ab";
static final String b = "c";
public static void main(String[] args) {
String c = a + b;
System.out.println(c == "abc");
}
}
这段代码将输出true
,如果把final
删掉,则变为false
。这其实也说明了如果能在编译时唯一确定,那么在编译为.class文件这个过程中编译器会做对应的优化。
其实不仅是上面这种情况会做优化,对于字符串常量相加:
String str= "ab" + "cd" + "ef";
会变成
String str= "abcdef";
对于字符串变量相加:
String str = "abcdef";
for(int i=0; i<1000; i++) {
str = str + i;
}
会变成
String str = "abcdef";
for(int i=0; i<1000; i++) {
str = (new StringBuilder(String.valueOf(str))).append(i).toString();
}
除了上述这些之外,其实我们写代码时写下的字面字符串常量,比如"abc"
,会在编译期间会被优化为一个String对象。具体来说,如果多个字面字符串常量的值相同,编译器会将它们合并为同一个String对象,并在类的常量池中生成对应的常量引用。
intern()
方法
JDK7前后的intern()
方法在底层实现上不同了,该方法用来返回字符串在常量池中的引用。
intern()
方法签名:public native String intern()();
JDK7以前
当通过String构造函数创建一个字符串对象时,intern()
方法首先会在常量池中是否有对应的字符串常量,如果没有,就复制一份到常量池,然后返回常量池中复制得到的字符串的引用
JDK7以后
如果常量池中没有,那么只会在常量池中记录该String对象的引用,并不会再在常量池中复制一份字符串常量。
#==
与equals()
方法的区别
有了以上基础,接下来理解这两者的区别会简单很多。
对于复杂类型的数据,==
其实就是比较其二者的hash code。
默认hash code生成貌似是根据对象的内存地址。
所以new String("abc")
和"abc"
并不相等,因为它们根本上来说并不是同一对象,但很明显我们一般认为只要字符串内容一样就算做同一对象,所以这个时候就应该重写equals()
方法来判断是否相等,但这个需求由于过于常见,Java已经帮我们做了——重写equals()
方法。
这个其实也就是那个老生常谈的问题——为什么重写
hashCode()
方法就一定要重新equals()
方法。这是因为重写
hashCode()
方法后,以后集合之类的API就按这个来判断对象是否相等,但我们知道hash函数总是难免发生碰撞,所以不同对象可能也会发生计算出来的hash code相等,这个时候如果我们没有重写equals()
方法,那么基本上百分百就会出问题。因为默认
equals()
方法是按内存地址来判断两对象是否相等的,那么肯定不会相等,因为我们认为Person p1 = new Person("小明");
和Person p2 = new Person("小明");
是同一个对象,即使我们确实创建的是两个对象。