引入:我们都知道,创建字符串对象的两种方式:
String name1 = "jack";
String name2 = new String("jack");
1. String name1 = “jack”;的解读
- "jack"是字符串常量,也叫字符串字面量,位于字符串常量池中
- name1是一个栈变量,指向这个常量池中的String对象
- 若常量池中已存在该字面量,则返回常量池中的该字面量地址给name1
- 若池中没有该字面量,则在池中创建该字面量,再把地址返回给name1
- jdk1.7及以后,常量池从方法区移入堆中
2. String name2 = new String(“jack”);的解读
- 首先是创建一个字面量"jack",这也是一次创建对象的过程,会按照1中的步骤来,也就是说,会从常量池中查找是否有"jack"这个字面量,结果找到了。
- 把"jack"这个字面量作为String参数,调用对应的的构造器,第二次来创建String对象
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
- 这就是说,新建的这个String对象,它虽然在常量池之外,但是它维护的域value[]却与常量池中的"jack"对象的value[]是同一个字符数组,这进一步节省了空间。
- 也就是说,这第二种创建String对象的方式其实会创建两次String对象,只不过作为过渡的字面量,如果没有引用指向它,很快就会被GC回收。此例中,由于name1指向池中的这个字面量,它并不会被GC回收
- 如何证明两者指向了同一字符数组?(代码中输出内容都会备注在后面)
String s1 = "你是大笨蛋!";
String s2 = new String("你是大笨蛋!");
//此时只要获得两个String的value[],再比较一下唯一标识,就知道是不是同一个字符数组了!
//问题是,value是private的,只能通过反射了
Field valueStringField = String.class.getDeclaredField("value");
valueStringField.setAccessible(true);//开通权限
char[] value1 = (char[])valueStringField.get(s1);
char[] value2 = (char[])valueStringField.get(s2);
System.out.println(value1 == value2);//true
System.out.println(System.identityHashCode(value1));//2133927002
System.out.println(System.identityHashCode(value2));//2133927002
//两者完全一致,说明是一个字符数组
- 什么是
System.identityHashCode(obj)
?为什么不用obj.hashCode()
?下面解答:
3. hashCode()
-
hashCode的总合同是:
- 同一个对象多次调用hashCode,必须保证值一致,但不必从一次执行到另一次执行保持一致
- 两个对象equals为真 <=> 两个对象的hashCode值相等,该规则表明,当重写hashCode和equals方法时,必须使此规则得到贯彻
- 两个对象不同,不必保证其hashCode值不同
- 用数学充分不必要,充要条件来表示就是:
同一对象 => hashCode值相等
obj1.equals(obj2)为true <=> hashCode值相等(需要该类重写了equals()方法,没有重写的equals()与 ==等价)
不同对象,但equals为true => hashCode值相等
-
String重写了hashCode()、equals()方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
- 因此不能用hahCode值来判断两个对象是否为同一对象,由此引出
System.identityHashCode(obj)
4. System.identityHashCode(obj)
- 这是对象的唯一标识,不管该对象是否重写了hashCode方法,只要两个对象地址不同,那么该identityHashCode(obj)就不同。
5. String.intern()方法
- 如何将一个在堆中新建的String对象放入常量池中?
String string = new String("傻子");
String poolString = string.intern();
- 该方法会在常量池中新建一个新的String对象,但是value[]和堆中对象是同一个
- 先证明两者是不同对象:
System.out.println(string == string.intern());//false
System.out.println(System.identityHashCode(string));//21685669
System.out.println(System.identityHashCode(string.intern()));//2133927002
- 再证明两者的value仍然是同一个:(private域只能通过反射来获得了)
Field valueStringField = String.class.getDeclaredField("value");
valueStringField.setAccessible(true);//打卡通道
char[] value1 = (char[]) valueStringField.get(string);
char[] value2 = (char[]) valueStringField.get(string.intern());
System.out.println(System.identityHashCode(value1));//325040804
System.out.println(System.identityHashCode(value2));//325040804
- 这证明:两个不同String对象的value[]域指向同一个字符数组