1. 程式人生 > >謎題31:循環者的鬼魂

謎題31:循環者的鬼魂

使用 spa clas 右移 自動 怎樣 成了 無符號 comm

請提供一個對i的聲明,將下面的循環轉變為一個無限循環:


while (i != 0) {

    i >>>= 1;

}

回想一下,>>>=是對應於無符號右移操作符的賦值操作符。0被從左移入到由移位操作而空出來的位上,即使被移位的負數也是如此。

這個循環比前面三個循環要稍微復雜一點,因為其循環體非空。在其循環題中,i的值由它右移一位之後的值所替代。為了使移位合法,i必須是一個整數類型(byte、char、short、int或long)。無符號右移操作符把0從左邊移入,因此看起來這個循環執行叠代的次數與最大的整數類型所占據的位數相同,即64次。如果你在循環的前面放置如下的聲明,那麽這確實就是將要發生的事情:


long i = -1; // -1L has all 64 bits set

你怎樣才能將它轉變為一個無限循環呢?解決本謎題的關鍵在於>>>=是一個復合賦值操作符。(復合賦值操作符包括*=、/=、%=、+=、-=、<<=、>>=、>>>=、&=、^=和|=。)有關混合操作符的一個不幸的事實是,它們可能會自動地執行窄化原始類型轉換[JLS 15.26.2],這種轉換把一種數字類型轉換成了另一種更缺乏表示能力的類型。窄化原始類型轉換可能會丟失級數的信息,或者是數值的精度[JLS 5.1.3]。

讓我們更具體一些,假設你在循環的前面放置了下面的聲明:


short i = -1;

因為i的初始值((short)0xffff)是非0的,所以循環體會被執行。在執行移位操作時,第一步是將i提升為int類型。所有算數操作都會對short、byte和char類型的操作數執行這樣的提升。這種提升是一個拓寬原始類型轉換,因此沒有任何信息會丟失。這種提升執行的是符號擴展,因此所產生的int數值是0xffffffff。然後,這個數值右移1位,但不使用符號擴展,因此產生了int數值0x7fffffff。最後,這個數值被存回到i中。為了將int數值存入short變量,Java執行的是可怕的窄化原始類型轉換,它直接將高16位截掉。這樣就只剩下(short)oxffff了,我們又回到了開始處。循環的第二次以及後續的叠代行為都是一樣的,因此循環將永遠不會終止。

如果你將i聲明為一個short或byte變量,並且初始化為任何負數,那麽這種行為也會發生。如果你聲明i為一個char,那麽你將無法得到無限循環,因為char是無符號的,所以發生在移位之前的拓寬原始類型轉換不會執行符號擴展。

總之,不要在short、byte或char類型的變量之上使用復合賦值操作符。因為這樣的表達式執行的是混合類型算術運算,它容易造成混亂。更糟的是,它們執行將隱式地執行會丟失信息的窄化轉型,其結果是災難性的。

對語言設計者的教訓是語言不應該自動地執行窄化轉換。還有一點值得好好爭論的是,Java是否應該禁止在short、byte和char變量上使用復合賦值操作符。

謎題31:循環者的鬼魂