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 的方法來執行引用標識的比較。  

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