下面的程序計算了一個循環(huán)的迭代次數(shù),并且在該循環(huán)終止時將這個計數(shù)值打印了出來。那么,它打印的是什么呢?
public class InTheLoop {
public static final int END = Integer.MAX_VALUE;
public static final int START = END - 100;
public static void main(String[] args) {
int count = 0;
for (int i = START; i <= END; i++)
count++;
System.out.println(count);
}
}
如果你沒有非常仔細地查看這個程序,你可能會認為它將打印100,因為END比START大100。如果你稍微仔細一點,你可能會發(fā)現(xiàn)該程序沒有使用典型的循環(huán)慣用法。大多數(shù)的循環(huán)會在循環(huán)索引小于終止值時持續(xù)運行,而這個循環(huán)則是在循環(huán)索引小于或等于終止值時持續(xù)運行。所以它會打印101,對嗎?
嗯,根本不對。如果你運行該程序,就會發(fā)現(xiàn)它壓根就什么都沒有打印。更糟的是,它會持續(xù)運行直到你撤銷它為止。它從來都沒有機會去打印count,因為在打印它的語句之前插入的是一個無限循環(huán)。
問題在于這個循環(huán)會在循環(huán)索引(i)小于或等于Integer.MAX_VALUE時持續(xù)運行,但是所有的int變量都是小于或等于Integer.MAX_VALUE的。因為它被定義為所有int數(shù)值中的值。當i達到Integer.MAX_VALUE,并且再次被執(zhí)行增量操作時,它就有繞回到了Integer.MIN_VALUE。
如果你需要的循環(huán)會迭代到int數(shù)值的邊界附近時,你是使用一個long變量作為循環(huán)索引。只需將循環(huán)索引的類型從int改變?yōu)閘ong就可以解決該問題,從而使程序打印出我們所期望的101:
for (long i = START; i <= END; i++)
更一般地講,這里的教訓(xùn)就是int不能表示所有的整數(shù)。無論你在何時使用了一個整數(shù)類型,都要意識到其邊界條件。如果其數(shù)值下溢或是上溢了,會怎么樣呢?所以通常是使用一個取之范圍更大的類型。(整數(shù)類型包括byte、char、short、int和long。)
不使用long類型的循環(huán)索引變量也可以解決該問題,但是它看起來并不那么漂亮:
int i = START;
do {
count++;
}while (i++ != END);
如果清晰性和簡潔性占據(jù)了極其重要的地位,那么在這種情況下使用一個long類型的循環(huán)索引幾乎總是方案。
但是有一個例外:如果你在所有的(或者幾乎所有的)int數(shù)值上迭代,那么使用int類型的循環(huán)索引的速度大約可以提高一倍。下面是將f函數(shù)作用于所有40億個int數(shù)值上的慣用法:
//Apply the function f to all four billion int values
int i = Integer.MIN_VALUE;
do {
f(i);
}while (i++ != Integer.MAX_VALUE);
該謎題對語言設(shè)計者的教訓(xùn)與謎題3相同:可能真的值得去考慮,應(yīng)該對那些不會在產(chǎn)生溢出時而不拋出異常的算術(shù)運算提供支持。同時,可能還值得去考慮,應(yīng)該對那些在整數(shù)值范圍之上進行迭代的循環(huán)進行特殊設(shè)計,就像許多其他語言所做的那樣。
public class InTheLoop {
public static final int END = Integer.MAX_VALUE;
public static final int START = END - 100;
public static void main(String[] args) {
int count = 0;
for (int i = START; i <= END; i++)
count++;
System.out.println(count);
}
}
如果你沒有非常仔細地查看這個程序,你可能會認為它將打印100,因為END比START大100。如果你稍微仔細一點,你可能會發(fā)現(xiàn)該程序沒有使用典型的循環(huán)慣用法。大多數(shù)的循環(huán)會在循環(huán)索引小于終止值時持續(xù)運行,而這個循環(huán)則是在循環(huán)索引小于或等于終止值時持續(xù)運行。所以它會打印101,對嗎?
嗯,根本不對。如果你運行該程序,就會發(fā)現(xiàn)它壓根就什么都沒有打印。更糟的是,它會持續(xù)運行直到你撤銷它為止。它從來都沒有機會去打印count,因為在打印它的語句之前插入的是一個無限循環(huán)。
問題在于這個循環(huán)會在循環(huán)索引(i)小于或等于Integer.MAX_VALUE時持續(xù)運行,但是所有的int變量都是小于或等于Integer.MAX_VALUE的。因為它被定義為所有int數(shù)值中的值。當i達到Integer.MAX_VALUE,并且再次被執(zhí)行增量操作時,它就有繞回到了Integer.MIN_VALUE。
如果你需要的循環(huán)會迭代到int數(shù)值的邊界附近時,你是使用一個long變量作為循環(huán)索引。只需將循環(huán)索引的類型從int改變?yōu)閘ong就可以解決該問題,從而使程序打印出我們所期望的101:
for (long i = START; i <= END; i++)
更一般地講,這里的教訓(xùn)就是int不能表示所有的整數(shù)。無論你在何時使用了一個整數(shù)類型,都要意識到其邊界條件。如果其數(shù)值下溢或是上溢了,會怎么樣呢?所以通常是使用一個取之范圍更大的類型。(整數(shù)類型包括byte、char、short、int和long。)
不使用long類型的循環(huán)索引變量也可以解決該問題,但是它看起來并不那么漂亮:
int i = START;
do {
count++;
}while (i++ != END);
如果清晰性和簡潔性占據(jù)了極其重要的地位,那么在這種情況下使用一個long類型的循環(huán)索引幾乎總是方案。
但是有一個例外:如果你在所有的(或者幾乎所有的)int數(shù)值上迭代,那么使用int類型的循環(huán)索引的速度大約可以提高一倍。下面是將f函數(shù)作用于所有40億個int數(shù)值上的慣用法:
//Apply the function f to all four billion int values
int i = Integer.MIN_VALUE;
do {
f(i);
}while (i++ != Integer.MAX_VALUE);
該謎題對語言設(shè)計者的教訓(xùn)與謎題3相同:可能真的值得去考慮,應(yīng)該對那些不會在產(chǎn)生溢出時而不拋出異常的算術(shù)運算提供支持。同時,可能還值得去考慮,應(yīng)該對那些在整數(shù)值范圍之上進行迭代的循環(huán)進行特殊設(shè)計,就像許多其他語言所做的那樣。