String是经常用到的类,下面根据一个demo,分析String的某些原理,示例代码如下:
String str1 = new String("abc");
String str2 = "abc";
String str3 = "a";
String str4 = str3 + "bc";
String str5 = "a" + "b" + "c";
System.out.println(str1 == str2.intern());
System.out.println(str2 == str1);
System.out.println(str2 == str4);
System.out.println(str1 == str1.intern());
System.out.println(str2 == str2.intern());
System.out.println(str2 == str5);
System.out.println(str1.hashCode() + "\n" +str2.hashCode() +"\n" +str4.hashCode()+"\n"+str5.hashCode());
运行结果如下:
false
false
false
true
false
true
true
96354
96354
96354
96354
下面对上面的代码片段进行分析:
1、String的定义为:final class String
这说明,String是不可变的,即定义了,其值不会再变化
2、String str1 = new String(“abc”);
其过程如下:在编译期间,jvm先在常量池来查找是否存在“abc”,若不存在,则新增一个存放“abc”的空间,然后在运行的时候,new了一个空间,将常量池中的“abc”复制到堆里面,再在栈中存放堆的地址。
固,System.out.println(str2 == str1); 结果为false
3、String str2 = “abc”;
其过程如下:在编译期间,jvm先在常量池来查找是否存在“abc”,若不存在,则新增一个存放“abc”的空间,然后在运行的时候,将栈的地址指向常量池空间的地址。
4、String str4 = str3 + “bc”;
其过程如下:新增一个StringBuilder对象,使用append方法,将“bc”拼接到“a”,再使用toString()方法new一个对象,将堆内存中新增一个空间,将“abc”复制到堆里面,再在栈中存放堆的地址。
固,System.out.println(str2 == str4);结果为false
5、String str5 = “a” + “b” + “c”;
这段代码,需要借助于反编译工具查看反编译后的结果了,如下为反编译后的结果:
String str5 = "abc";
这说明编译器,对于非变量字符串的拼接,是有做优化的,所以这个地方, str5 = “a” + “b” + “c”;等价于String str5 = “abc”;
固:System.out.println(str2 == str5);结果为true
6、String的intern方法
在《深入理解java虚拟机》这本书里面,有介绍过,String的intern方法,其作用为:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用;否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
分析第一个对比:System.out.println(str1 == str1.intern()); str1,栈里面保存的是堆地址,str1.intern(),返回的是常量池里面的地址,而str1在堆里面才保存的常量池里面的地址。固此时结果为false
分析第二个对比:System.out.println(str2 == str2.intern());返回的是常量池里面的地址,而此时已经有了“abc”在字符串常量池里面,固str4.intern()返回的是“abc”对象的引用。
7、关于hashcode()
看示例得知,value为“abc”的几个对象hashcode最终都是一个值,说明String对hashcode有做重写,类似如下代码:
public static int hashCode(byte[] ==value==) {
int h = 0;
byte[] var2 = value;
int var3 = value.length;
for(int var4 = 0; var4 < var3; ++var4) {
byte v = var2[var4];
h = 31 * h + (v & 255);
}
return h;
}
这段代码说明,String的hashcode值是由value决定的。value固定,则hashcode值固定。
8、关于equals方法
equals方法,也做了重写,从下面代码得知,equal方法,实际上也是对具体的value进行比较:
public static boolean equals(byte[] ==value==, byte[] other) {
if (value.length == other.length) {
for(int i = 0; i < value.length; ++i) {
if (value[i] != other[i]) {
return false;
}
}
return true;
} else {
return false;
}
}
对比Object类的equals方法:
public boolean equals(Object obj) {
return this == obj;
}
说明:如果没有对Object的equals方法进行重写,则默认对比的是对象(所有类的父类为Object类),如果对equals方法进行重写,则需按照具体规则进行判断。
另外,值得注意的是,如果重写equals方法,那么一定就要重写hashcode方法,原因为hashcode需满足如下规则:
1)幂等性:同一对象多次调用hashcode方法,返回的值需保持一致
2)如果equals比较结果为true,那么hashcode比较结果也要一定为true
3)如果equals比较结果为false,那么hashcode也不一定不等
基于此,如果equals重写,那么hashcode也必须重写。