java谜题

谜题 12ABC

 

 

这个谜题要问的是一个悦耳的问题,下面的程序将打印什么呢?

 

public class ABC{

 

public static void main(String[] args){

 

String letters = "ABC";

 

char[] numbers = {'1', '2', '3'};

 

System.out.println(letters + " easy as " + numbers);

 

}

 

}

 

可能大家希望这个程序打印出 ABC easy as 123。遗憾的是,它没有。如果你运

 

行它,就会发现它打印的是诸如 ABC easy as [C@16f0472 之类的东西。为什么

 

这个输出会如此丑陋?


尽管 char 是一个整数类型,但是许多类库都对其进行了特殊处理,因为 char

 

数值通常表示的是字符而不是整数。例如,将一个 char 数值传递给 println

 

法会打印出一个 Unicode 字符而不是它的数字代码。字符数组受到了相同的特殊

 

处理:println char[]重载版本会打印出数组所包含的所有字符,而

 

String.valueOfStringBuffer.appendchar[]重载版本的行为也是类似的。

 

然而,字符串连接操作符在这些方法中没有被定义。该操作符被定义为先对它的

 

两个操作数执行字符串转换,然后将产生的两个字符串连接到一起。对包括数组

 

在内的对象引用的字符串转换定义如下[JLS 15.18.1.1]

 

如果引用为 null,它将被转换成字符串"null"。否则,该转换的执行就像是不

 

用任何参数调用该引用对象的 toString 方法一样;但是如果调用 toString 方法

 

的结果是 null,那么就用字符串"null"来代替。

 

那么,在一个非空 char 数组上面调用 toString 方法会产生什么样的行为呢?数

 

组是从 Object 那里继承的 toString 方法[JLS 10.7],规范中描述到:返回一

 

个字符串,它包含了该对象所属类的名字,'@'符号,以及表示对象散列码的一

 

个无符号十六进制整数”[Java-API]。有关 Class.getName 的规范描述到:在

 

char[]类型的类对象上调用该方法的结果为字符串"[C"。将它们连接到一起就形

 

成了在我们的程序中打印出来的那个丑陋的字符串。

 

有两种方法可以订正这个程序。你可以在调用字符串连接操作之前,显式地将一

 

个数组转换成一个字符串:

 

System.out.println(letters + " easy as " +

 

String.valueOf(numbers));

 

或者,你可以将 System.out.println 调用分解为两个调用,以利用 println

 

char[]重载版本:

 

System.out.print(letters + " easy as ");

 

System.out.println(numbers);

 

请注意,这些订正只有在你调用了 valueOf println 方法正确的重载版本的情

 

况下,才能正常运行。换句话说,它们严格依赖于数组引用的编译期类型。

 

下面的程序说明了这种依赖性。看起来它像是所描述的第二种订正方式的具体实

 

现,但是它产生的输出却与最初的程序所产生的输出一样丑陋,因为它调用的是

 

println Object 重载版本,而不是 char[]重载版本。

 

class ABC2{

 

public static void main(String[] args){

 

String letters = "ABC";

 

Object numbers = new char[] { '1', '2', '3' };

 

System.out.print(letters + " easy as ");

 

System.out.println(numbers);

 

}


 

}


总之,char 数组不是字符串。要想将一个 char 数组转换成一个字符串,就要调

 

String.valueOf(char[])方法。某些类库中的方法提供了对 char 数组的类似

 

字符串的支持,通常是提供一个 Object 版本的重载方法和一个 char[]版本的重

 

载方法,而之后后者才能产生我们想要的行为。

 

对语言设计者的教训是:char[]类型可能应该覆写 toString 方法,使其返回数

 

组中包含的字符。更一般地讲,数组类型可能都应该覆写 toString 方法,使其

 

返回数组内容的一个字符串表示。

 

 

谜题 13:畜牧场

 

 

George Orwell 的《畜牧场(Animal Farm)》一书的读者可能还记得老上校的

 

宣言:所有的动物都是平等的。下面的 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);

 

}

 

}

 

对该程序的表面分析可能会认为它应该打印出 Animal are equal: true。毕竟,

 

pigdog都是finalstring类型变量,它们都被初始化为字符序列length:

 

10。换句话说,被 pig dog 引用的字符串是且永远是彼此相等的。然而,==

 

操作符测试的是这两个对象引用是否正好引用到了相同的对象上。在本例中,它

 

们并非引用到了相同的对象上。

 

你可能知道 String 类型的编译期常量是内存限定的。换句话说,任何两个 String

 

类型的常量表达式,如果标明的是相同的字符序列,那么它们就用相同的对象引

 

用来表示。如果用常量表达式来初始化 pig dog,那么它们确实会指向相同的

 

对象,但是 dog 并不是用常量表达式初始化的。既然语言已经对在常量表达式中

 

允许出现的操作作出了限制,而方法调用又不在其中,那么,这个程序就应该打

 

Animal are equal: false,对吗?

 

嗯,实际上不对。如果你运行该程序,你就会发现它打印的只是 false,并没有

 

其它的任何东西。它没有打印 Animal are equal: 。它怎么会不打印这个字符

 

串字面常量呢?毕竟打印它才是正确的呀!谜题 11 的解谜方案包含了一条暗示:

 

+ 操作符,不论是用作加法还是字符串连接操作,它都比 == 操作符的优先级高。

 

因此,println 方法的参数是按照下面的方式计算的:

 

System.out.println(("Animals are equal: " + pig) == dog);

 

这个布尔表达式的值当然是 false,它正是该程序的所打印的输出。


有一个肯定能够避免此类窘境的方法:在使用字符串连接操作符时,总是将非平

 

凡的操作数用括号括起来。更一般地讲,当你不能确定你是否需要括号时,应该

 

选择稳妥地做法,将它们括起来。如果你在 println 语句中像下面这样把比较部

 

分括起来,它将产生所期望的输出 Animals are equal: false

 

System.out.println("Animals are equal: " + (pig == dog));

 

可以论证,该程序仍然有问题。

 

如果可以的话,你的代码不应该依赖于字符串常量的内存限定机制。内存限定机

 

制只是设计用来减少虚拟机内存占有量的,它并不是作为程序员可以使用的一种

 

工具而设计的。就像这个谜题所展示的,哪一个表达式会产生字符串常量并非总

 

是很显而易见。

 

更糟的是,如果你的代码依赖于内存限定机制实现操作的正确性,那么你就必须

 

仔细地了解哪些域和参数必定是内存限定的。编译器不会帮你去检查这些不变

 

量,因为内存限定的和不限定的字符串使用相同的类型(String)来表示的。这

 

些因在内存中限定字符串失败而导致的 bug 是非常难以探测到的。

 

在比较对象引用时,你应该优先使用 equals 方法而不是 == 操作符,除非你需

 

要比较的是对象的标识而不是对象的值。通过把这个教训应用到我们的程序中,

 

我们给出了下面的 println 语句,这才是它应该具有的模样。很明显,在用这种

 

方式订正了该程序之后,它将打印出 true

 

System.out.println("Animals are equal: " + pig.equals(dog));

 

这个谜题对语言设计者来说有两个教训。

 

?6?1        字符串连接的优先级不应该和加法一样。这意味着重载 + 操作符来执行 字符串连接是有问题的,就像在谜题 11 中提到的一样。 ?6?1        还有就是,对于不可修改的类型,例如 String,其引用的等价性比值的 等价性更加让人感到迷惑。也许 == 操作符在被应用于不可修改的类型时 应该执行值比较。要实现这一点,一种方法是将 == 操作符作为 equals 方法的简便写法,并提供一个单独的类似于 System.identityHashCode 的方法来执行引用标识的比较。  

你可能感兴趣的:(Java解惑)