天天看點

Effective Java 第三版——59. 熟悉并使用Java類庫

Tips

書中的源代碼位址:https://github.com/jbloch/effective-java-3e-source-code

注意,書中的有些代碼裡方法是基于Java 9 API中的,是以JDK 最好下載下傳 JDK 9以上的版本。

59. 熟悉并使用Java類庫

假設想要生成0到某個上界之間的随機整數。對于這個常見的任務,許多程式員會編寫一個類似這樣的方法:

// Common but deeply flawed!
static Random rnd = new Random();

static int random(int n) {
    return Math.abs(rnd.nextInt()) % n;
}
           

這個方法可能看起來不錯,但它有三個缺陷。 首先,如果n是一個比較小的2的乘方,則随機數的序列将在相當短的時間段後開始重複。 第二個缺陷是,如果n不是2的乘方,平均而言,某些數字會比其他數字出現得更加頻繁。 如果n很大,這種效果可能非常明顯。 以下程式有力地證明了這一點,該程式在精心選擇的範圍内生成了100萬個随機數,然後列印出有多少個數字落在範圍的上半部分:

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方法正常工作,程式将列印接近50萬的數字,但如果運作它,你會發現它列印的數字接近666,666。random方法生成的三分之二數字落在其範圍的上半部分!

random方法的第三個缺陷是,在極少數情況下,它可能會災難性地失敗,傳回超出指定範圍之外的數字。 這是因為該方法嘗試通過調用

Math.abs

rnd.nextInt()

傳回的值映射到非負整數。 如果

nextInt()

傳回

Integer.MIN_VALUE

,則

Math.abs

也會傳回

Integer.MIN_VALUE

,假設n不是2的乘方,取模運算符(%)将傳回負數。 這幾乎肯定會導緻程式失敗,并且可能難以重制。

要編寫一個糾正這些缺陷的random方法的版本,你必須知道關于僞随機數生成器,數論和二進制補碼算法的知識。幸運的是,你不必這樣做 —— 它已經為你完成了,就是

Random.nextInt(int)

方法。你不必關心它如何完成其​​工作的細節(,如果您很好奇,可以研究文檔或源代碼)。一位具有算法背景的進階工程師花費了大量時間來設計,實作和測試這種方法,然後向該領域的幾位專家展示,以確定其正确性。然後,這個類庫經過了beta測試,釋出,并被數百萬程式員廣泛使用了近二十年。該方法尚未發現任何缺陷,但如果發現了缺陷,将在下一個版本中修複。通過使用标準類庫,可以利用編寫類庫專家的知識以及在前人使用它的經驗。

從Java 7開始,就不應再使用Random了。 對于大多數用途,選擇的随機數生成器現在是

ThreadLocalRandom

。 它産生更高品質的随機數,而且速度非常快。 在我的機器上,它比Random快3.6倍。 對于fork-join池和并行流的應用,請使用

SplittableRandoms

使用這些類庫的第二個好處是,不必浪費時間為那些與你的工作關聯不大的問題上,而去編寫專門的解決方案。如果像大多數程式員一樣,那麼甯願将時間花在應用程式上,而不是底層内容上。

使用标準類庫的第三個優點是,它們的性能會随着時間的推移而不斷提高,而你無需付出任何努力。 因為許多人使用它們并且因為它們被用于行業标準基準測試,是以提供這些類庫的組織有強烈的動力使它們運作得更快。 多年來,許多Java平台類庫都經過重寫,有時甚至是重複編寫,進而顯着提升性能。

使用類庫的第四個優點是它們傾向于随着時間的推移不斷增加功能。 如果某個類庫遺失了某些東西,開發人員社群就會知道它,并且可能會在後續版本中添加缺少的功能。

使用标準庫的最後一個好處是,可以将代碼放在主流中。這樣的代碼更容易被開發人員閱讀、維護和重用。

鑒于所有這些優點,使用類庫設施優先于專門實作似乎是合乎邏輯的,但許多程式員并不這樣做。為什麼不呢? 也許他們不知道類庫工具設施的存在。 在每個主要版本中,都會向類庫中添加許多特性,了解這些新增特性是非常值得的。每次有Java平台的主要版本釋出時,都會釋出一個web頁面來描述它的新特性。這些頁面非常值得一讀[Java8-feat, Java9-feat]。為了強調這一點,假設你想編寫一個程式來列印指令行中指定的URL的内容(這大緻與Linux系統下curl指令相同)。 在Java 9之前,這段代碼有點乏味,但在Java 9中,

transferTo

方法被添加到

InputStream中

。 以下是使用此新方法執行此任務的完整程式:

// 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);
    }
}
           

這些類庫太大了,以至于無法學習所有文檔[Java9-api],但每個程式員都應該熟悉

java.lang

java.util

java.io

及其子包的基礎知識。 可以根據需要擷取其他類庫的知識。 總結類庫設施超出了本條目的範圍,這些設施多年來已經發展得非常龐大。

幾個類庫特别值得一提。 集合Collection架構和流Stream類庫(條目4——-48)應該是每個程式員的基本工具包的一部分,

java.util.concurrent中

的并發實用程式的也應如此。 該軟體既包含了用于簡化多線程程式設計任務的進階實用程式,還包括偏底層的原語,以允許專家編寫自己的進階并發抽象。 條目 80和81會讨論

java.util.concurrent

的進階部分。

有時,類庫設施可能無法滿足你的需求。需求越專門化,發生這種情況的可能性就越大。雖然第一個沖動應該是使用這些類庫,但是如果已經了解了它們在某些領域提供的功能,而這些功能不能滿足你的需求,那麼可以使用另一種實作。任何有限的類庫集所提供的功能總是存在漏洞。如果你在Java平台庫中找不到你需要的東西,你的下一個選擇應該是尋找高品質的第三方庫,比如谷歌的優秀的開源Guava類庫[Guava]。如果無法在任何适當的類庫中找到所需的功能,可能别無選擇,你隻能自己實作了。

總而言之,不要重新發明輪子。 如果需要做一些似乎應該相當常見的事情,那麼類庫中可能已經有了一個可以滿足你需求的工具。 如果有,請使用它; 如果不知道,請檢查。 一般來說,類庫代碼可能比您自己編寫的代碼更好,并且可能會随着時間的推移而改進。 這并不反映你作為程式員的能力。 規模經濟決定了類庫代碼得到的關注遠遠超過大多數開發人員可以承擔的相同的功能。