1. 程式人生 > >Effective Java 第三版——15. 使類和成員的可訪問性最小化

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

control 常見 以及 操作 數據表示 potential info 四大 access

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

技術分享圖片

類和接口是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屬性引用的對象是不可變的。

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