谜题13:动物庄园
来看下面的java程序将会打印什么?
public class AnimalFarm{
public static void main(String[] args){
final String pig = "length: 10";
final String dog = "length: " + pig.length();
System.out.println("Animals are equal: " + pig == dog);
}
}
对该程序进行表面分析后,我们可能会说这个程序打印的是"Animals are equal: true",因为pig和dog都是final和String类型变量,并且它们都被初始化为字符序列"length: 10",也就是说被pig和dog引用的字符串是彼此相等的。但是,==操作符并不测试两个对象是否相等,而是测试两个对象引用是否相同。换句话说,它测试的是这两个对象引用是否正好引用到同一对象上。而在本例中,它们并非引用到同一对象上。
我们应该都清楚String类型的编译器常量是内存限定的。任何两个String类型的常量表达式,如果指定的是相同的字符序列,那么它们就用同一对象引用来表示。但在上面的示例程序中,pig是用常量表达式初始化的,而dog并不是用常量表达式初始化的。既然语言已经对在常量表达式中允许出现的操作作出了限制,而方法调用又不在其中,那么,这个程序就应该打印Animal are equal: false,但实际上还是不对。运行程序后得到的结果是仅仅打印了false,并没有打印其它任何东西。在谜题11中提到过+操作符,不论是用作加法还是字符串连接操作,它都比==操作符的优先级高,因此println方法的参数是按照下面的方式计算的:
System.out.println(("Animals are equal: " + pig) == dog);
所以这个布尔表达式的值当然是false。所以我们在使用字符串连接操作符时,总是将不平凡的操作数用括号括起来。也就是说,当你不确定是否需要括号时,应当选择稳妥的做法,为它们添加一对括号。例如示例程序:
System.out.println("Animals are equal: " + (pig == dog));
即使我们将程序修改成了这样,该程序仍然是有问题的。如果可以的话,我们所写的代码应该很少依赖于字符串常量的内存限定机制。内存限定机制只是设计用来减少虚拟机内存占有量,并不是程序员的一种工具。就如该谜题所示,哪一个表达式会产生字符串常量并非总是显而易见的。如果你的代码依赖于内存限定机制实现操作的正确性,那么你就必须仔细地了解哪些域和参数必须是内存限定的。编译器不会帮你检查这些变量,因为内存限定的和不限定的字符串使用相同的类型(String)来表示。在内存中限定字符串失败而导致的bug是很难以探测的。
从示例程序中,我们还应该注意的是在比较对象引用时,应该优先使用equal方法而不是==操作符,除非需要比较的是对象的标识而不是对象的值。所以我们修正后的println语句应当是:
System.out.println("Animals are equal: " + pig.equal(dog));