Effective Java 第三版——15. 使類和成員的可訪問性最小化

Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必不少人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到如今已經將近8年的時間,但隨着Java 6,7,8,甚至9的發佈,Java語言發生了深入的變化。
在這裏第一時間翻譯成中文版。供你們學習分享之用。java

Effective Java, Third Edition

類和接口是Java編程語言的核心。它們是抽象的基本單位。該語言提供了許多強大的元素,可使用它們來設計類和接口。本章包含指導原則,幫助你充分利用這些元素,使你的類和接口是可用的、健壯的和靈活的。程序員

15. 使類和成員的可訪問性最小化

將設計良好的組件與設計不佳的組件區分開來的最重要的因素是,組件將其內部數據和其餘組件的其餘實現細節隱藏起來。一個設計良好的組件隱藏了它的全部實現細節,乾淨地將它的API與它的實現分離開來。而後,組件只經過它們的API進行通訊,而且對彼此的內部工做一無所知。這一律念,被稱爲信息隱藏或封裝,是軟件設計的基本原則[Parnas72]。編程

信息隱藏很重要有不少緣由,其中大部分來源於它將組成系統的組件分離開來,容許它們被獨立地開發,測試,優化,使用,理解和修改。這加速了系統開發,由於組件能夠並行開發。它減輕了維護的負擔,由於能夠更快速地理解組件,調試或更換組件,而不用擔憂損害其餘組件。雖然信息隱藏自己並不會致使良好的性能,但它能夠有效地進行性能調整:一旦系統完成而且分析肯定了哪些組件致使了性能問題(條目 67),則能夠優化這些組件,而不會影響別人的正確的組件。信息隱藏增長了軟件重用,由於鬆耦合的組件一般在除開發它們以外的其餘環境中證實是有用的。最後,隱藏信息下降了構建大型系統的風險,由於即便系統不能運行,各個獨立的組件也多是可用的。數組

Java提供了許多機制來幫助信息隱藏。 訪問控制機制(access control mechanism)[JLS,6.6]指定了類,接口和成員的可訪問性。 實體的可訪問性取決於其聲明的位置,以及聲明中存在哪些訪問修飾符(private,protected和public)。 正確使用這些修飾符對信息隱藏相當重要。安全

經驗法則很簡單:讓每一個類或成員儘量地不可訪問。換句話說,使用盡量低的訪問級別,與你正在編寫的軟件的對應功能保持一致。編程語言

對於頂層(非嵌套的)類和接口,只有兩個可能的訪問級別:包級私有(package-private)和公共的(public)。若是你使用public修飾符聲明頂級類或接口,那麼它是公開的;不然,它是包級私有的。若是一個頂層類或接口能夠被作爲包級私有,那麼它應該是。經過將其設置爲包級私有,能夠將其做爲實現的一部分,而不是導出的API,你能夠修改它、替換它,或者在後續版本中消除它,而沒必要擔憂損害現有的客戶端。若是你把它公開,你就有義務永遠地支持它,以保持兼容性。模塊化

若是一個包級私有頂級類或接口只被一個類使用,那麼能夠考慮這個類做爲使用它的惟一類的私有靜態嵌套類(條目 24)。這將它的可訪問性從包級的全部類減小到使用它的一個類。可是,減小沒必要要的公共類的可訪問性要比包級私有的頂級類更重要:公共類是包的API的一部分,而包級私有的頂級類已是這個包實現的一部分了。性能

對於成員(屬性、方法、嵌套類和嵌套接口),有四種可能的訪問級別,在這裏,按照可訪問性從小到大列出:學習

  • private——該成員只能在聲明它的頂級類內訪問。
  • package-private——成員能夠從被聲明的包中的任何類中訪問。從技術上講,若是沒有指定訪問修飾符(接口成員除外,它默認是公共的),這是默認訪問級別。
  • protected——成員能夠從被聲明的類的子類中訪問(受一些限制,JLS,6.6.2),以及它聲明的包中的任何類。
  • public——該成員能夠從任何地方被訪問。

在仔細設計你的類的公共API以後,你的反應應該是讓全部其餘成員設計爲私有的。 只有當同一個包中的其餘類真的須要訪問成員時,須要刪除私有修飾符,從而使成員包成爲包級私有的。 若是你發現本身常常這樣作,你應該從新檢查你的系統的設計,看看另外一個分解可能產生更好的解耦的類。 也就是說,私有成員和包級私有成員都是類實現的一部分,一般不會影響其導出的API。 可是,若是類實現Serializable接口(條目 86和87),則這些屬性能夠「泄漏(leak)」到導出的API中。測試

對於公共類的成員,當訪問級別從包私有到受保護級時,可訪問性會大大增長。 受保護(protected)的成員是類導出的API的一部分,而且必須永遠支持。 此外,導出類的受保護成員表示對實現細節的公開承諾(條目 19)。 對受保護成員的需求應該相對較少。

有一個關鍵的規則限制了你減小方法訪問性的能力。 若是一個方法重寫一個超類方法,那麼它在子類中的訪問級別就不能低於父類中的訪問級別[JLS,8.4.8.3]。 這對於確保子類的實例在父類的實例可用的地方是可用的(Liskov替換原則,見條目 15)是必要的。 若是違反此規則,編譯器將在嘗試編譯子類時生成錯誤消息。 這個規則的一個特例是,若是一個類實現了一個接口,那麼接口中的全部類方法都必須在該類中聲明爲public。

爲了便於測試你的代碼,你可能會想要讓一個類,接口或者成員更容易被訪問。 這沒問題。 爲了測試將公共類的私有成員指定爲包級私有是能夠接受的,可是提升到更高的訪問級別倒是不可接受的。 換句話說,將類,接口或成員做爲包級導出的API的一部分來促進測試是不可接受的。 幸運的是,這不是必須的,由於測試能夠做爲被測試包的一部分運行,從而得到對包私有元素的訪問。

公共類的實例屬性不多公開(條目 16)。若是一個實例屬性是非final的,或者是對可變對象的引用,那麼經過將其公開,你就放棄了限制能夠存儲在屬性中的值的能力。這意味着你放棄了執行涉及該屬性的不變量的能力。另外,當屬性被修改時,就放棄了採起任何操做的能力,所以公共可變屬性的類一般不是線程安全的。即便屬性是final的,而且引用了一個不可變的對象,經過使它公開,你就放棄切換到不存在屬性的新的內部數據表示的靈活性。

一樣的建議適用於靜態屬性,但有一個例外。 假設常量是類的抽象的一個組成部分,你能夠經過public static final屬性暴露常量。 按照慣例,這些屬性的名字由大寫字母組成,字母用下劃線分隔(條目 68)。 很重要的一點是,這些屬性包含基本類型的值或對不可變對象的引用(條目 17)。 包含對可變對象的引用的屬性具備非final屬性的全部缺點。 雖然引用不能被修改,但引用的對象能夠被修改,並會帶來災難性的結果。

請注意,非零長度的數組老是可變的,因此類具備公共靜態final數組屬性,或返回這樣一個屬性的訪問器是錯誤的。 若是一個類有這樣的屬性或訪問方法,客戶端將可以修改數組的內容。 這是安全漏洞的常見來源:

// Potential security hole!
public static final Thing[] VALUES = { ... };

要當心這樣的事實,一些IDE生成的訪問方法返回對私有數組屬性的引用,致使了這個問題。 有兩種方法能夠解決這個問題。 你可使公共數組私有並添加一個公共的不可變列表:

private static final Thing[] PRIVATE_VALUES = { ... };

public static final List<Thing> VALUES =

Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

或者,能夠將數組設置爲private,並添加一個返回私有數組拷貝的公共方法:

private static final Thing[] PRIVATE_VALUES = { ... };

public static final Thing[] values() {
    return PRIVATE_VALUES.clone();
}

要在這些方法之間進行選擇,請考慮客戶端可能如何處理返回的結果。 哪一種返回類型會更方便? 哪一個會更好的表現?

在Java 9中,做爲模塊系統(module system)的一部分引入了兩個額外的隱式訪問級別。模塊包含一組包,就像一個包包含一組類同樣。模塊能夠經過模塊聲明中的導出(export)聲明顯式地導出某些包(這是module-info.java的源文件中包含的約定)。模塊中的未導出包的公共和受保護成員在模塊以外是不可訪問的;在模塊中,可訪問性不受導出(export)聲明的影響。使用模塊系統容許你在模塊之間共享類,而不讓它們對整個系統可見。在未導出的包中,公共和受保護的公共類的成員會產生兩個隱式訪問級別,這是普通公共和受保護級別的內部相似的狀況。這種共享的需求是相對少見的,而且能夠經過從新安排包中的類來消除。

與四個主要訪問級別不一樣,這兩個基於模塊的級別主要是建議(advisory)。 若是將模塊的JAR文件放在應用程序的類路徑而不是其模塊路徑中,那麼模塊中的包將恢復爲非模塊化行爲:包的公共類的全部公共類和受保護成員都具備其普通的可訪問性,無論包是否由模塊導出[Reinhold,1.2]。 新引入的訪問級別嚴格執行的地方是JDK自己:Java類庫中未導出的包在模塊以外真正沒法訪問。

對於典型的Java程序員來講,不只程序模塊所提供的訪問保護存在侷限性,並且在本質上是很大程度上建議性的;爲了利用它,你必須把你的包組合成模塊,在模塊聲明中明確全部的依賴關係,從新安排你的源碼樹層級,並採起特殊的行動來適應你的模塊內任何對非模塊化包的訪問[Reinhold ,3]。 如今說模塊是否會在JDK以外獲得普遍的使用還爲時尚早。 與此同時,除非你有迫切的須要,不然彷佛最好避免它們。

總而言之,應該儘量地減小程序元素的可訪問性(在合理範圍內)。 在仔細設計一個最小化的公共API以後,你應該防止任何散亂的類,接口或成員成爲API的一部分。 除了做爲常量的公共靜態final屬性以外,公共類不該該有公共屬性。 確保public static final屬性引用的對象是不可變的。

相關文章
相關標籤/搜索