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 方法,使其

 

返回數組內容的一個字符串表示。

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