JAVA字符謎題3:畜牧場

字號:

George Orwell的《畜牧場(Animal Farm)》一書的讀者可能還記得老上校的宣言:“所有的動物都是平等的?!毕旅娴腏ava程序試圖要測試這項宣言。那么,它將打印出什么呢?
    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。畢竟,pig和dog都是final的string類型變量,它們都被初始化為字符序列“l(fā)ength: 10”。換句話說,被pig和dog引用的字符串是且永遠是彼此相等的。然而,==操作符測試的是這兩個對象引用是否正好引用到了相同的對象上。在本例中,它們并非引用到了相同的對象上。
    你可能知道String類型的編譯期常量是內(nèi)存限定的。換句話說,任何兩個String類型的常量表達式,如果標明的是相同的字符序列,那么它們就用相同的對象引用來表示。如果用常量表達式來初始化pig和dog,那么它們確實會指向相同的對象,但是dog并不是用常量表達式初始化的。既然語言已經(jīng)對在常量表達式中允許出現(xiàn)的操作作出了限制,而方法調(diào)用又不在其中,那么,這個程序就應該打印Animal are equal: false,對嗎?
    嗯,實際上不對。如果你運行該程序,你就會發(fā)現(xiàn)它打印的只是false,并沒有其它的任何東西。它沒有打印Animal are equal: 。它怎么會不打印這個字符串字面常量呢?畢竟打印它才是正確的呀!謎題11的解謎方案包含了一條暗示:+ 操作符,不論是用作加法還是字符串連接操作,它都比 == 操作符的優(yōu)先級高。因此,println方法的參數(shù)是按照下面的方式計算的:
    System.out.println(("Animals are equal: " + pig) == dog);
     這個布爾表達式的值當然是false,它正是該程序的所打印的輸出。
    有一個肯定能夠避免此類窘境的方法:在使用字符串連接操作符時,總是將非平凡的操作數(shù)用括號括起來。更一般地講,當你不能確定你是否需要括號時,應該選擇穩(wěn)妥地做法,將它們括起來。如果你在println語句中像下面這樣把比較部分括起來,它將產(chǎn)生所期望的輸出Animals are equal: false :
    System.out.println("Animals are equal: " + (pig == dog));
     可以論證,該程序仍然有問題。
    如果可以的話,你的代碼不應該依賴于字符串常量的內(nèi)存限定機制。內(nèi)存限定機制只是設計用來減少虛擬機內(nèi)存占有量的,它并不是作為程序員可以使用的一種工具而設計的。就像這個謎題所展示的,哪一個表達式會產(chǎn)生字符串常量并非總是很顯而易見。
    更糟的是,如果你的代碼依賴于內(nèi)存限定機制實現(xiàn)操作的正確性,那么你就必須仔細地了解哪些域和參數(shù)必定是內(nèi)存限定的。編譯器不會幫你去檢查這些不變量,因為內(nèi)存限定的和不限定的字符串使用相同的類型(String)來表示的。這些因在內(nèi)存中限定字符串失敗而導致的bug是非常難以探測到的。
    在比較對象引用時,你應該優(yōu)先使用equals方法而不是 == 操作符,除非你需要比較的是對象的標識而不是對象的值。通過把這個教訓應用到我們的程序中,我們給出了下面的println語句,這才是它應該具有的模樣。很明顯,在用這種方式訂正了該程序之后,它將打印出true:
    System.out.println("Animals are equal: " + pig.equals(dog));
     這個謎題對語言設計者來說有兩個教訓。
    字符串連接的優(yōu)先級不應該和加法一樣。這意味著重載 + 操作符來執(zhí)行字符串連接是有問題的,就像在謎題11中提到的一樣。
    還有就是,對于不可修改的類型,例如String,其引用的等價性比值的等價性更加讓人感到迷惑。也許 == 操作符在被應用于不可修改的類型時應該執(zhí)行值比較。要實現(xiàn)這一點,一種方法是將 == 操作符作為equals方法的簡便寫法,并提供一個單獨的類似于System.identityHashCode的方法來執(zhí)行引用標識的比較。