假設(shè)你希望產(chǎn)生位于0和某個(gè)上界之間的隨機(jī)整數(shù)。面對這個(gè)常見的任務(wù)击喂,許多程序員會編寫如下所示的方法:
// Common but deeply flawed!
static Random rnd = new Random();
static int random(int n) {
return Math.abs(rnd.nextInt()) % n;
}
這個(gè)方法看起來可能不錯(cuò)维苔,但是卻又三個(gè)缺點(diǎn),第一個(gè)缺點(diǎn)是懂昂,如果n是一個(gè)比較小的2的乘方介时,經(jīng)過一段相當(dāng)短的周期之后,它會產(chǎn)生的隨機(jī)數(shù)序列將會重復(fù)凌彬。第二個(gè)缺點(diǎn)是沸柔,如果n不是2的乘方,那么平均起來铲敛,有些數(shù)會比其他的數(shù)出現(xiàn)得更為頻繁褐澎。如果n比較大,這個(gè)缺點(diǎn)就會非常明顯伐蒋。這可以通過下面的程序直觀的體現(xiàn)出來工三,它會產(chǎn)生100萬個(gè)經(jīng)過精心指定的范圍內(nèi)的隨機(jī)數(shù)迁酸,并打印出有多少個(gè)數(shù)字落在隨機(jī)數(shù)取值范圍的前半部分:
public static void main(String[] args) {
int n = 2 * (Integer.MAX_VALUE / 3);
int low = 0;
for (int i = 0; i < 1000000; i++)
if (random(n) < n/2)
low++;
System.out.println(low);
}
如果random方法工作正常,這個(gè)程序打印出來的數(shù)將接近于100萬的一半俭正,但是如果真正運(yùn)行這個(gè)程序奸鬓,就會發(fā)現(xiàn)它打印出的數(shù)接近于666666。由于random方法產(chǎn)生的數(shù)字有三分之二落在隨機(jī)數(shù)取值范圍的前半部分段审。
random方法的第三個(gè)缺點(diǎn)是全蝶,在極少數(shù)情況下,它的失敗是災(zāi)難性的寺枉,因?yàn)闀祷匾粋€(gè)落在指定范圍之外的數(shù)抑淫。之所以如此,是因?yàn)檫@個(gè)方法試圖通過調(diào)用Math.abs姥闪,將rnd.nextInt()返回的值隱射為一個(gè)非負(fù)整數(shù)int始苇。如果nextInt()返回Integer.MIN_VALUE,那么Math.abs也會返回Integer.MAX_VALUE筐喳,假設(shè)n不是2的乘方催式,那么取模操作符(%)將返回一個(gè)負(fù)數(shù)。這幾乎肯定會使程序失敗避归,而且這種失敗很難重現(xiàn)荣月。
為了編寫能修正這三個(gè)缺點(diǎn)的random方法,有必要了解關(guān)于同余隨機(jī)數(shù)生成器梳毙、數(shù)論和2的求補(bǔ)算法的相關(guān)知識哺窄。幸運(yùn)的是,你并不需要自己來做這些工作账锹,已經(jīng)有現(xiàn)成的成果可以為你所用萌业。這一成果被稱作Random.nextInt(int)。你無須關(guān)系nextInt(int)的實(shí)現(xiàn)細(xì)節(jié)(如果你有強(qiáng)烈的好奇心奸柬,可以研究它的文檔或者源代碼)生年。具有算法背景的高級工程師已經(jīng)花了大量的時(shí)間來設(shè)計(jì)、實(shí)現(xiàn)和測試這個(gè)方法廓奕,然后經(jīng)過這個(gè)領(lǐng)域中的專家的審查抱婉,以確保它的正確性。之后桌粉,標(biāo)準(zhǔn)類庫經(jīng)過了Beta測試并正式發(fā)行授段,幾年之間已經(jīng)有成千上萬的程序員在使用它。在這個(gè)方法中還沒有發(fā)現(xiàn)過缺陷番甩,但是侵贵,如果將來發(fā)現(xiàn)有缺陷,在下一個(gè)發(fā)行版本就會修正這些缺陷缘薛。通過使用標(biāo)準(zhǔn)類庫窍育,可以充分利用這些編寫標(biāo)準(zhǔn)的專家的知識卡睦,以及在你之前的其他人的使用經(jīng)驗(yàn)
。
從Java7開始漱抓,就不應(yīng)該再使用Random了表锻。現(xiàn)在選擇隨機(jī)數(shù)生成器時(shí),大多使用ThreadLocalRandom
乞娄。它會產(chǎn)生更高質(zhì)量的隨機(jī)數(shù)瞬逊,并且速度非常快仪或。在我的機(jī)器上确镊,比Random快了3.6倍。對于Fork Join Pool和并行Stream范删,則使用SplittableRandom蕾域。
使用標(biāo)準(zhǔn)庫的第二個(gè)好處是,不必浪費(fèi)時(shí)間為那些與工作不太相關(guān)的問題提供特別的解決方案到旦。就像大多數(shù)程序員一樣旨巷,應(yīng)該把時(shí)間放在應(yīng)用程序上,而不是底層細(xì)節(jié)上添忘。
使用標(biāo)準(zhǔn)庫的第三個(gè)好處是采呐,它們的性能往往會隨著時(shí)間的推移而不斷提高,無須你做任何努力搁骑。因?yàn)樵S多人在使用它們懈万,并且是當(dāng)工業(yè)標(biāo)準(zhǔn)在使用,所以提供這些標(biāo)準(zhǔn)類庫的組織有強(qiáng)烈的動機(jī)要使它們運(yùn)行得更快靶病。這些年來,許多Java平臺類庫已經(jīng)被重新編寫了口予,有時(shí)候是重復(fù)編寫娄周,而是在性能上有了顯著的提高。
使用標(biāo)準(zhǔn)庫的第四個(gè)好處是沪停,它們會隨著時(shí)間的推移而增加新的功能煤辨。如果類庫中漏掉了某些功能,開發(fā)者社區(qū)就會把這些缺點(diǎn)公式出來木张,漏掉的功能就會添加到后續(xù)的發(fā)行版本中众辨。
使用標(biāo)準(zhǔn)類庫的最后一個(gè)好處是,可以使自己的代碼融入主流舷礼。這樣的代碼更易讀鹃彻、更易維護(hù)、更易被大多數(shù)的開發(fā)人員重用妻献。
既然有那么多的有點(diǎn)蛛株,使用標(biāo)準(zhǔn)類庫機(jī)制而不選擇專門的實(shí)現(xiàn)团赁,這顯然是符合邏輯的,然而還是有相當(dāng)一部分的程序員沒有這么做谨履。為什么呢欢摄?可能它們并不知道有這些類庫機(jī)制的存在。在每個(gè)重要的發(fā)行版本中笋粟,都會有許多新的特性被加入到類庫中怀挠,所以與這些新特性保持同步是值得的
。每當(dāng)Java平臺有重要的發(fā)行時(shí)害捕,都會發(fā)布一個(gè)網(wǎng)頁來說明新的特性绿淋。舉個(gè)例子,假設(shè)想要編寫一個(gè)程序吨艇,用它打印出命令行中指定的一條URL的內(nèi)容(Linux的curl命令的作用大體如此)躬它。在Java9之前,這些代碼有點(diǎn)煩瑣东涡,但是Java9在InputStream中增加了transferTo方法冯吓。下面就是利用這個(gè)新方法完成這項(xiàng)任務(wù)的完整程序:
// Printing the contents of a URL with transferTo, added in Java 9
public static void main(String[] args) throws IOException {
try (InputStream in = new URL(args[0]).openStream()) {
in.transferTo(System.out);
}
}
這些標(biāo)準(zhǔn)類庫太龐大了,以至于不可能學(xué)完所有的文檔疮跑,但是每個(gè)程序員都應(yīng)該熟悉java.lang组贺、java.util、java.io及其子包中的內(nèi)容祖娘。
關(guān)于其他類庫的知識可以根據(jù)需要隨時(shí)學(xué)習(xí)失尖。總結(jié)類庫中的機(jī)制超出了本條目的范圍渐苏,幾年來它們已經(jīng)發(fā)展得十分龐大了掀潮。
其中有幾個(gè)類庫值得一提。Collections Framework(集合框架)和Stream類庫(詳見第45條至48條)應(yīng)該成為每一為程序員基本工具箱中的一部分琼富,同樣也應(yīng)該成為java.util.concurrent中并發(fā)機(jī)制的組成部分仪吧。這個(gè)包既包含高級的并發(fā)工具來簡化多線程的編程任務(wù),還包含低級別的并發(fā)基本類型鞠眉,運(yùn)行專家們自己編寫更高級的并發(fā)抽象薯鼠。關(guān)于java.util.concurrent的高級部分,請參閱第80條和第81條械蹋。
在某些情況下出皇,一個(gè)類庫工具并不能滿足你的需求。你的需求越是特殊哗戈,這種情形就越有可能發(fā)生郊艘。雖然你的第一個(gè)念頭應(yīng)該是使用標(biāo)準(zhǔn)類庫,但是,如果你在觀察了它們的某些領(lǐng)域所提供的功能之后暇仲,確定不能滿足需要步做,你就得使用其他的實(shí)現(xiàn)。任何一組類庫所提供的功能總是難免會有遺漏奈附。如果你在Java類庫中找不到所需要的功能全度,下一個(gè)選擇應(yīng)該是在高級的第三方類庫中去尋找,比如Goole優(yōu)秀的開源Guava類庫斥滤。如果在所有相應(yīng)的類庫中都無法找到你所需的功能将鸵,就只能自己實(shí)現(xiàn)這些功能了。
總而言之佑颇,不要重復(fù)發(fā)明輪子
顶掉。如果你要做的事情看起來是十分常見的,有可能類庫中已經(jīng)有某個(gè)類完成了這樣的工作挑胸。如果確實(shí)是這樣痒筒,就使用現(xiàn)成的;如果還不清楚是否存在這樣的類茬贵,就去查一查簿透。一般而言,類庫的代碼可能比你自己編寫的代碼更好一些解藻,并且會隨著時(shí)間的推移而不斷改進(jìn)老充。這并不是在質(zhì)疑你作為一個(gè)程序員的能力。從經(jīng)濟(jì)角度的分析表明:類庫代碼受到的關(guān)注遠(yuǎn)遠(yuǎn)超過大多數(shù)普通程序員在同樣功能上所能給予的投入螟左。