Effective Java 第三版——57. 最小化局部變量的做用域

Tips
書中的源代碼地址:https://github.com/jbloch/effective-java-3e-source-code
注意,書中的有些代碼裏方法是基於Java 9 API中的,因此JDK 最好下載 JDK 9以上的版本。java

Effective Java, Third Edition

9. 通用編程

這一章專門討論Java語言的具體細節。討論了局部變量、控制結構、類庫、數據類型以及兩種Java語言以外工具:反射和本地方法。最後,討論了優化和命名慣例。git

57. 最小化局部變量的做用域

這條目在性質上相似於條目 15,即「最小化類和成員的可訪問性」。經過最小化局部變量的做用域,能夠提升代碼的可讀性和可維護性,並下降出錯的可能性。程序員

較早的編程語言(如C)要求必須在代碼塊的頭部聲明局部變量,而且一些程序員繼續習慣這樣作。 這是一個值得改進的習慣。 做爲提醒,Java容許你在任何合法的語句的地方聲明變量(as does C, since C99)。github

用於最小化局部變量做用域的最強大的技術是再首次使用的地方聲明它。 若是變量在使用以前被聲明,那就變得更加混亂—— 這也會對試圖理解程序的讀者來說,又增長了一件分散他們注意力的事情。 到使用該變量時,讀者可能不記得變量的類型或初始值。編程

過早地聲明局部變量可能致使其做用域不只過早開始並且結束太晚。 局部變量的做用域從聲明它的位置延伸到封閉塊的末尾。 若是變量在使用它的封閉塊以外聲明,則在程序退出該封閉塊後它仍然可見。若是在其預約用途區域以前或以後意外使用變量,則後果多是災難性的。編程語言

幾乎每一個局部變量聲明都應該包含一個初始化器。若是尚未足夠的信息來合理地初始化一個變量,那麼應該推遲聲明,直到認爲能夠這樣作。這個規則的一個例外是try-catch語句。若是一個變量被初始化爲一個表達式,該表達式的計算結果能夠拋出一個已檢查的異常,那麼該變量必須在try塊中初始化(除非所包含的方法能夠傳播異常)。若是該值必須在try塊以外使用,那麼它必須在try塊以前聲明,此時它還不能被「合理地初始化」。例如,參照條目 65中的示例。工具

循環提供了一個特殊的機會來最小化變量的做用域。傳統形式的for循環和for-each形式都容許聲明循環變量,將其做用域限制在須要它們的確切區域。 (該區域由循環體和for關鍵字與正文之間的括號中的代碼組成)。所以,若是循環終止後不須要循環變量的內容,那麼優先選擇for循環而不是while循環測試

例如,下面是遍歷集合的首選方式(條目 58):優化

// Preferred idiom for iterating over a collection or array
for (Element e : c) {
    ... // Do Something with e
}

若是須要訪問迭代器,也許是爲了調用它的remove方法,首選的習慣用法,使用傳統的for循環代替for-each循環:3d

// Idiom for iterating when you need the iterator
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
    Element e = i.next();
    ... // Do something with e and i
}

要了解爲何這些for循環優於while循環,請考慮如下代碼片斷,其中包含兩個while循環和一個bug:

Iterator<Element> i = c.iterator();
while (i.hasNext()) {
    doSomething(i.next());
}
...
Iterator<Element> i2 = c2.iterator();
while (i.hasNext()) {             // BUG!
    doSomethingElse(i2.next());
}

第二個循環包含一個複製粘貼錯誤:它初始化一個新的循環變量i2,可是使用舊的變量i,不幸的是,它仍在範圍內。 生成的代碼編譯時沒有錯誤,而且在不拋出異常的狀況下運行,但它作錯了。 第二個循環不是在c2上迭代,而是當即終止,給出了c2爲空的錯誤印象。 因爲程序無聲地出錯,所以錯誤可能會長時間沒法被檢測到。

若是將相似的複製粘貼錯誤與for循環(for-each循環或傳統循環)結合使用,則生成的代碼甚至沒法編譯。第一個循環中的元素(或迭代器)變量不在第二個循環中的做用域中。下面是它與傳統for循環的示例:

for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
    Element e = i.next();
    ... // Do something with e and i
}
...

// Compile-time error - cannot find symbol i
for (Iterator<Element> i2 = c2.iterator(); i.hasNext(); ) {
    Element e2 = i2.next();
    ... // Do something with e2 and i2
}

此外,若是使用for循環,那麼發送這種複製粘貼錯誤的可能性要小得多,由於沒有必要在兩個循環中使用不一樣的變量名。 循環是徹底獨立的,所以重用元素(或迭代器)變量名稱沒有壞處。 事實上,這樣作一般很流行。

for循環比while循環還有一個優勢:它更短,加強了可讀性。

下面是另外一種循環習慣用法,它最小化了局部變量的做用域:

for (int i = 0, n = expensiveComputation(); i < n; i++) {
    ... // Do something with i;
}

關於這個作法須要注意的重要一點是,它有兩個循環變量,i和n,它們都具備徹底相同的做用域。第二個變量n用於存儲第一個變量的限定值,從而避免了每次迭代中冗餘計算的代價。做爲一個規則,若是循環測試涉及一個方法調用,而且保證在每次迭代中返回相同的結果,那麼應該使用這種用法。

最小化局部變量做用域的最終技術是保持方法小而集中。 若是在同一方法中組合兩個行爲(activities),則與一個行爲相關的局部變量可能會位於執行另外一個行爲的代碼範圍內。 爲了防止這種狀況發生,只需將方法分爲兩個:每一個行爲對應一個方法。

相關文章
相關標籤/搜索