JAVA循環(huán)謎題35:一分鐘又一分鐘

字號:

下面的程序在模仿一個簡單的時鐘。它的循環(huán)變量表示一個毫秒計數(shù)器,其計數(shù)值從0開始直至一小時中包含的毫秒數(shù)。循環(huán)體以定期的時間間隔對一個分鐘計數(shù)器執(zhí)行增量操作。最后,該程序?qū)⒋蛴》昼娪嫈?shù)器。那么它會打印出什么呢?
    public class Clock {
     public static void main(String[] args) {
     int minutes = 0;
     for (int ms = 0; ms < 60*60*1000; ms++)
     if (ms % 60*1000 == 0)
     minutes++;
     System.out.println(minutes);
     }
    }
    在這個程序中的循環(huán)是一個標(biāo)準(zhǔn)的慣用for循環(huán)。它步進(jìn)毫秒計數(shù)器(ms),從0到一小時中的毫秒數(shù),即3,600,000,包括前者但是不包括后者。循環(huán)體看起來是在每當(dāng)毫秒計數(shù)器的計數(shù)值是60,000(一分鐘內(nèi)所包含毫秒數(shù))的倍數(shù)時,對分鐘計數(shù)器(minutes)執(zhí)行增量操作。這在循環(huán)的生命周期內(nèi)總共發(fā)生了3,600,000/60,000次,即60次,因此你可能期望程序打印出60,畢竟,這就是一小時所包含的分鐘數(shù)。但是,該程序的運行卻會告訴你另外一番景象:它打印的是60000。為什么它會如此頻繁地對minutes執(zhí)行了增量操作呢?
    問題在于那個布爾表達(dá)式(ms % 60*1000 == 0)。你可能會認(rèn)為這個表達(dá)式等價于(ms % 60000 == 0),但是它們并不等價。取余和乘法操作符具有相同的優(yōu)先級[JLS 15.17],因此表達(dá)式ms % 60*1000 等價于(ms % 60)*1000。如果(ms % 60)等于0的話,這個表達(dá)式就等于0,因此循環(huán)每60次迭代就對minutes執(zhí)行增量操作。這使得最終的結(jié)果相差1000倍。
    訂正該程序的最簡單的方式就是在布爾表達(dá)式中插入一對括號,以強(qiáng)制規(guī)定計算的正確順序:
    if (ms % (60 * 1000) == 0)
     minutes++;
    然而,有一個更好的方法可以訂正該程序。用被恰當(dāng)命名的常量來替代所有的魔幻數(shù)字:
    public class Clock {
     private static final int MS_PER_HOUR = 60 * 60 * 1000;
     private static final int MS_PER_MINUTE = 60 * 1000;
     public static void main(String[] args) {
     int minutes = 0;
     for (int ms = 0; ms < MS_PER_HOUR; ms++)
     if (ms % MS_PER_MINUTE == 0)
     minutes++;
     System.out.println(minutes);
     }
    }
    之所以要在最初的程序中展現(xiàn)表達(dá)式 ms % 60*1000,是為了誘使你去認(rèn)為乘法比取余有更高的優(yōu)先級。然而,編譯器是忽略空格的,所以千萬不要使用空格來表示分組,要使用括號。空格是靠不住的,而括號是從來不說謊的。