謎題32:循環者的詛咒
請提供一個對i的聲明,將下面的循環轉變為一個無限循環:
while (i <= j && j <= i && i != j) {
}
噢,不,不要再給我看起來不可能的循環了!如果i <= j 並且 j <= i,i不是肯定等於j嗎?這一屬性對實數肯定有效。事實上,它是如此地重要,以至於它有這樣的定義:實數上的≤關系是反對稱的。Java的<=操作符在5.0版之前是反對稱的,但是這從5.0版之後就不再是了。
直到5.0版之前,Java的數字比較操作符(<、<=、>和>=)要求它們的兩個操作數都是原始數字類型的(byte、char、short、int、long、float和double)[JLS 15.20.1]。但是在5.0版中,規範作出了修改,新規範描述道:每一個操作數的類型必須可以轉換成原始數字類型[JLS 15.20.1,5.1.8]。問題難就難在這裏了。
在5.0版中,自動包裝(autoboxing)和自動反包裝(auto-unboxing)被添加到了Java語言中。如果你對它們並不了解,請查看:http://java.sun.com/j2se/5.0/docs/guide/language/autoboxing.html [Boxing]。<=操作符在原始數字類型集上仍然是反對稱的,但是現在它還被應用到了被包裝的數字類型上。(被包裝的數字類型有:Byte、Character、Short、Integer、Long、Float和Double。)<=操作符在這些類型的操作數上不是反對稱的,因為Java的判等操作符(==和!=)在作用於對象引用時,執行的是引用ID的比較,而不是值的比較。
讓我們更具體一些,下面的聲明賦予表達式(i <= j && j <= i && i != j)的值為true,從而將這個循環變成了一個無限循環:
Integer i = new Integer(0);
Integer j = new Integer(0);
前兩個子表達式(i <= j 和j <= i)在i和j上執行解包轉換[JLS 5.1.8],並且在數字上比較所產生的int數值。i和j都表示0,所以這兩個子表達式都被計算為true。第三個子表達式(i != j)在對象引用i和j上執行標識比較,因為它們都初始化為一個新的Integer實例,因此,第三個子表達式同樣也被計算為true,循環也就永遠地環繞下去了。
你可能會感到奇怪,為什麽語言規範沒有修改為:當判等操作符作用於被包裝的數字類型時,它們執行的是值比較。答案很簡單:兼容性。當一種語言被廣泛使用之後,以違反現有規範的方式去改變現有程序的行為是讓人無法接受的。下面的程序過去總是保證可以打印false,因此它必須繼續保持此特征:
public class ReferenceComparison {
public static void main(String[] args) {
System.out.println(
new Integer(0) == new Integer(0));
}
}
判等操作符在其兩個操作數中只有一個是被包裝的數字類型,而另一個是原始類型時,執行的確實是數值比較。因為這在5.0版之前是非法的,所有在這裏沒有任何兼容性的問題。讓我們更具體一些,下面的程序在1.4版中是非法的,而在5.0版中將打印true:
public class ValueComparison {
public static void main(String[] args) {
System.out.println(
new Integer(0) == 0);
}
}
總之,當兩個操作數都是被包裝的數字類型時,數值比較操作符和判等操作符的行為存在著根本的差異:數值比較操作符執行的是值比較,而判等操作符執行的是引用標識的比較。
對語言設計者來說,如果判等操作符一直執行的都是數值比較(謎題13),那麽生活可能就要簡單得多、快樂得多。也許真正的教訓應該是:語言設計者應該擁有高質量的水晶球,以預測語言的未來,並且做出相應的設計決策。嚴肅一點地講,語言設計者應該考慮語言可能會如何演化,並且應該努力去最小化在演化之路上的各種制約影響。
謎題32:循環者的詛咒