java字符谜题

谜题 11:最后的笑声

 

 

下面的程序将打印出什么呢?

 

public class LastLaugh{

 

public static void main(String[] args){

 

System.out.print("H"+"a");

 

System.out.print('H'+'a');

 

}

 

}

 

你可能会认为这个程序将打印 HaHa。该程序看起来好像是用两种方式连接了 H

 

a,但是你所见为虚。如果你运行这个程序,就会发现它打印的是 Ha169。那

 

么,为什么它会产生这样的行为呢?

 

正如我们所期望的,第一个对 System.out.print 的调用打印的是 Ha:它的参数

 

是表达式"H"+"a",显然它执行的是一个字符串连接。而第二个对

 

System.out.print 的调用就是另外一回事了。问题在于'H''a'是字符型字面

 

常量,因为这两个操作数都不是字符串类型的,所以 + 操作符执行的是加法而

 

不是字符串连接。

 

编译器在计算常量表达式'H'+'a'时,是通过我们熟知的拓宽原始类型转换将两

 

个具有字符型数值的操作数('H''a')提升为 int 数值而实现的。从 char

 

int 的拓宽原始类型转换是将 16 位的 char 数值零扩展到 32 位的 int。对于'H'

 

char 数值是 72,而对于'a'char 数值是 97,因此表达式'H'+'a'等价于 int

 

常量 72 + 97,或 169

 

站在语言的立场上,若干个 char 和字符串的相似之处是虚幻的。语言所关心的

 

是,char 是一个无符号 16 位原始类型整数——仅此而已。对类库来说就不尽如

 

此了,类库包含了许多可以接受 char 参数,并将其作为 Unicode 字符处理的方

 

法。

 

那么你应该怎样将字符连接在一起呢?你可以使用这些类库。例如,你可以使用

 

一个字符串缓冲区:

 

StringBuffer sb = new StringBuffer();

 

sb.append('H');

 

sb.append('a');


 

System.out.println(sb);


这么做可以正常运行,但是显得很丑陋。其实我们还是有办法去避免这种方式所

 

产生的拖沓冗长的代码。 你可以通过确保至少有一个操作数为字符串类型,来

 

强制 + 操作符去执行一个字符串连接操作,而不是一个加法操作。这种常见的

 

惯用法用一个空字符串("")作为一个连接序列的开始,如下所示:

 

System.out.println("" + 'H' + 'a');

 

这种惯用法可以确保子表达式都被转型为字符串。尽管这很有用,但是多少有一

 

点难看,而且它自身可能会引发某些混淆。你能猜到下面的语句将会打印出什么

 

吗?如果你不能确定,那么就试一下:

 

System.out.print("2 + 2 = " + 2+2);

 

如果使用的是 JDK 5.0,你还可以使用

 

System.out.printf("%c%c", 'H', 'a');

 

总之,使用字符串连接操作符使用格外小心。+ 操作符当且仅当它的操作数中至

 

少有一个是 String 类型时,才会执行字符串连接操作;否则,它执行的就是加

 

法。如果要连接的没有一个数值是字符串类型的,那么你可以有几种选择:

 

?6?1        预置一个空字符串; ?6?1        将第一个数值用 String.valueOf 显式地转换成一个字符串; ?6?1        使用一个字符串缓冲区; ?6?1        或者如果你使用的 JDK 5.0,可以用 printf 方法。  

这个谜题还包含了一个给语言设计者的教训。操作符重载,即使在 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 方法,使其

 

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章