问题引入
请看下面的代码清单1
<span style="white-space:pre"> </span>public void test1(){
String a = "a" + "b" + 1;
String b = "ab1";
System.out.println(a == b);
}
上述这段代码来源自谢宇编著的书籍《Java特种兵》上册。
代码清单1中的输出是true
这是个考察Java基本功的问题,类似的问题还有很多,如清单2:
<span style="white-space:pre"> </span>//代码清单2
public static String getA() { return "a"; }
public void test2(){
String a = "a";
final String c = "a";
String b = a + "b";
String d = c + "b";
String e = getA() + "b";
String compare = "ab";
System.out.println(b == compare);
System.out.println(d == compare);
System.out.println(e == compare);
}
Output:
false
true
false
要理解这个问题,首先需要搞清楚这些:
1. 关于“==”是做什么的?
2. a和b在内存中是什么样的?
3. 编译时的优化方案
关于“==”
“==”是用于匹配内存单元上的内容,在Java语言中,当使用“==”匹配时,其实就是对比两个内存单元的内容是否一样。如果是原始类型byte、boolean、short、char、int、long、float、double,就是直接比较它们的值。如果是引用(Reference),比较的就是引用的值,“引用的值”可以被认为是对象的逻辑地址。如果两个引用发生“==”操作,就是比较相应的两个对象的地址值是否一样。
换一句话说,如果两个引用所保存的对象是同一个对象,则返回true,否则返回false。如果a、b两个引用都指向null,则也是返回true。
编译时的优化
在代码清单1中,a引用是通过“+”赋值的,b引用是通过直接赋值的,那么为什么a和b两个引用会指向同一个内存单元?这就是JVM的“编译时优化”。
当编译器在编译代码 String a = "a" + "b" + 1 时,会将其编译成 String a = "ab1" ,因为都是“常量”,编译器认为这3个常量叠加会得到固定的值,无需运行时再进行计算,所以就会这样优化。类似的还有 int i = 2 * 3 + 1,并不是在实际运行时再计算i的值,而是在编译时直接变成了i=7。
而在代码清单2中,b与compare比较时,由于compare是个常量,而b不是常量,原因是b = a + "b",a并不是一个常量,a中的值可以修改,因此编译器不会进行优化。
变量c有一个final修饰符,从定义上约束了c是不允许改变的,因此编译器会进行优化。
变量e的值来自一个方法,虽然方法内返回一个常量的引用,但是编译器并不会去看方法内部做了什么,所以编译器不会进行优化。
关于“equals()”
首先来看看Object中的equals()方法的实现:
public boolean equals(Object obj)
{
//调用equal的对象的地址和参数对象的地址是否相等
return (this == obj);
}
从源码中可以看出,Object类中equals()方法是使用“==”来匹配的,也就是说,如果不去重写equals()方法,那么默认的equals()操作就是对比对象的地址。
equals()方法的存在就是希望子类去重写这个方法的 ,而在String类中,就重写了equals()方法:
<span style="white-space:pre"> </span>public boolean equals(Object anObject) {
// 如果是同一个对象
if (this == anObject) {
return true;
}
// 如果传递进来的参数是String类的实例
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = count;// 字符串长度
if (n == anotherString.count) // 如果长度相等就进行比较
{
char v1[] = value;// 取每一个位置的字符
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) // 对于每一位置逐一比较
{
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
在JDK 1.6中,String.equals()的代码逻辑大致是这样的:
1. 判定传入的对象和当前对象是否为同一个对象,如果是就直接返回true;
2. 判定传入的对象类型是否为String,若不是则返回false;
3. 判定传入的String与当前String的长度是否一致,如不一致就返回false;
4. 循环对比两个字符串的char[]数组,逐个比较字符是否一致,如果不一致就直接返回false;
5. 循环结束都没有找到不匹配的,最后返回true。
参考资料:谢宇--《Java特种兵》