Effective Java(3rd)-Item15 最大限度減少類和成員的訪問性

??最重要的因素區(qū)別一個(gè)好的設(shè)計(jì)組件和不好的設(shè)計(jì)組件是組件隱藏了它的內(nèi)部數(shù)據(jù)和其他組件的實(shí)現(xiàn)細(xì)節(jié)。好的設(shè)計(jì)組件隱藏了它的所有實(shí)現(xiàn)夸赫,將它的API和實(shí)現(xiàn)完全分離。組件之間只通過API進(jìn)行通信咖城,并且不清楚各自的內(nèi)部工作茬腿。這個(gè)概念呼奢,被稱為信息隱藏或封裝,是軟件設(shè)計(jì)的基本原則[Parnas72]切平。
??信息隱藏很重要握础,有很多原因,其中很大部分原因是它將構(gòu)成系統(tǒng)的組件解耦悴品,允許它們單獨(dú)開發(fā)禀综,測試,優(yōu)化苔严,使用定枷,理解和修改。這加速了系統(tǒng)開發(fā)届氢,因?yàn)榻M件可以被并行開發(fā)欠窒。它減輕了維護(hù)的負(fù)擔(dān),因?yàn)榻M件可以被更快理解退子,調(diào)試或更換而不必?fù)?dān)心損害其他組件岖妄,雖然信息隱藏本身不會造成好的性能,但是它實(shí)現(xiàn)了有效的性能調(diào)整:一旦系統(tǒng)完成并分析確定那些組件造成了性能問題(item67)寂祥,這些組件可以在不影響其他正常組件的情況下進(jìn)行優(yōu)化荐虐。信息隱藏增加了軟件重用,因?yàn)闆]有緊密耦合的組件在其他環(huán)境中是有用的丸凭,除了它們開發(fā)的那些缚俏。最后,信息隱藏減少了構(gòu)建大型系統(tǒng)的風(fēng)險(xiǎn)贮乳,因?yàn)榧词瓜到y(tǒng)沒有構(gòu)建好忧换,獨(dú)立的組件也能成功。
??Java有許多設(shè)備來輔助信息隱藏向拆。 控制訪問機(jī)制[JLS, 6.6]指定了類亚茬,接口和成員變量的可訪問性。實(shí)體的可訪問性由其聲明的位置確定浓恳,如果有刹缝,聲明存在訪問修飾符(private, protected, 和 public)。正確使用這些修飾符是信息隱藏必不可少的颈将。
??經(jīng)驗(yàn)法則很簡單:讓每個(gè)類或成員盡可能無法訪問梢夯。換句話說,在編寫軟件正常運(yùn)行下晴圾,盡可能使用最低可訪問級別颂砸。
??對于頂級(非嵌套)類和接口,只有兩種可能的訪問級別:package-private 和public。如果你用public修飾符聲明頂級類或接口人乓,它將是公共的勤篮;否則,它將是包私有的色罚。如果一個(gè)定繼類或接口可以變?yōu)榘接械呐龅蓿撬蛻?yīng)該是。通過使其成為包私有戳护,你可以使它稱為實(shí)現(xiàn)的一部分而不是出口API金抡,你可以在后續(xù)版本中修改它,替換他或消除它腌且,而不用擔(dān)心影響現(xiàn)有的客戶端梗肝。如果你使其public,你有義務(wù)一直支持它以保留兼容性切蟋。
??如果只有一個(gè)類使用包私有的頂級類或接口统捶,考慮將頂級類成為使用它的唯一類的私有靜態(tài)嵌套類(item24) 。這降低了所有類到它的一個(gè)類的可訪問性柄粹。但是比包私有頂級類更重要的是降低public類的可訪問性:public類是包API的一部分喘鸟,而包私有類早已是它實(shí)現(xiàn)的一部分。
??對于成員變量(字段驻右,方法什黑,嵌套類和嵌套接口),有四種可訪問等級堪夭,如下按照增加訪問性的順序列出:
? private—成員變量只能從聲明它的頂級類中訪問愕把。
? package-private—成本變量能在聲明的包中的任意類訪問。技術(shù)上成為默認(rèn)訪問森爽,如果你沒有加上訪問修飾符恨豁,這就是默認(rèn)訪問級別(除了默認(rèn)的為public的接口除外)。
? protected—成員變量能從聲明的類和子類中訪問(有一些限制[JLS, 6.6.2])以及聲明的類的包中的任何類都能訪問爬迟。
? public—任何地方任何類都能訪問橘蜜。
??仔細(xì)設(shè)計(jì)你的類的公共API后,你的反應(yīng)應(yīng)該是讓所有其他成員都為private付呕。只有當(dāng)同一個(gè)報(bào)另一個(gè)類真的需要訪問成為计福,你應(yīng)該去掉private修飾符,使得成員變得package-private徽职。如果你發(fā)現(xiàn)你自己經(jīng)常這么做象颖,你應(yīng)該重新檢查你的系統(tǒng)的設(shè)計(jì),看看另一個(gè)分解是否產(chǎn)生更好彼此分離的類姆钉。也就是說说订,private和package-private成員都是類實(shí)現(xiàn)的一部分抄瓦,但通常不會影響出口API。然而克蚂,如果類實(shí)現(xiàn)了Serializable闺鲸,這些字段可能會“泄漏”到出口API(item86item87 )筋讨。
??對于public類的成員埃叭,當(dāng)訪問等級從package-private 到 protected時(shí),可訪問性大大增加悉罕。受保護(hù)的成員是類出口API的一部分赤屋,必須永久支持。此外壁袄,出口類的protected成員表示對實(shí)現(xiàn)細(xì)節(jié)的公開承諾(item19)类早。對protected成員的需求相對比較罕見。
??
??有一個(gè)關(guān)鍵規(guī)則限制你降低方法的可訪問性的能力嗜逻。如果一個(gè)方法覆寫了超類的方法涩僻,它不能在子類中比父類的訪問性低[JLS, 8.4.8.3]。這是必要的栈顷,因?yàn)檫@樣可以確保子類實(shí)例在父類實(shí)例中的任何地方都是可用的(里氏替換原則)逆日,如果你違反了這個(gè)規(guī)則,當(dāng)你嘗試編譯子類時(shí)萄凤,編譯器將生成錯誤消息室抽。這個(gè)規(guī)則的特例是如果一個(gè)類實(shí)現(xiàn)了一個(gè)接口,所有這個(gè)類的方法都必須在類中聲明為public靡努。
??為了方便測試你的代碼坪圾,除了其他必要的,你可能傾向使類惑朦,接口或成員擁有更多訪問性兽泄。這一點(diǎn)很好。為了對其測試漾月,可以將public類的private成員package-private病梢,但是提高可訪問性是不可接受的。換句話說栅屏,為了便于測試飘千,將類,接口或成員作為出口API的一部分是不可接受的栈雳。幸運(yùn)的是护奈,這也沒有必要,因?yàn)闇y試可以作為被被測試包的一部分運(yùn)行哥纫,從而獲得對其package-private元素的訪問霉旗。
??public類的實(shí)例字段應(yīng)該幾乎不為public(item16) .如果一個(gè)實(shí)例字段是nonfinal的或它是一個(gè)可變對象的引用,使其public,你就放棄了可以在字段中存儲值得能力了厌秒。這意味著你放棄強(qiáng)制執(zhí)行涉及該字段的不變量的功能读拆。此外,你放棄在修改字段時(shí)采取任何操作的能力鸵闪。所有具有public可變字段的類通常不是線程安全的檐晕。即使字段是final的,引用了一個(gè)不可變對象蚌讼,讓其oublic辟灰,你就放棄切換到不存在該字段的新內(nèi)部數(shù)據(jù)表示的靈活性。
??同樣的建議適用于靜態(tài)字段篡石,但有一個(gè)例外芥喇。你 可以通過public static final字段暴露常量,假設(shè)常量形成了類提供的抽象組成部分凰萨。按照慣例继控,這些字段的名字由大寫字母組成,單詞用下劃線分割(item68) 胖眷。這些字段包含原始值或不可變對象的引用至關(guān)重要(item17) 武通。
??包含對可變對象的引用的字段具有nonfinal字段的所有缺點(diǎn)。雖然無法修改引用瘦材,但是修改引用的對象會帶來災(zāi)難性的結(jié)果厅须。
??請注意,非零長度數(shù)組總是可變的食棕,所以類擁有一個(gè)public static final 數(shù)組字段或但會此類字段的訪問器是錯誤的朗和。如果一個(gè)類擁有這樣的一個(gè)字段或訪問器,客戶端將有可能修改數(shù)組的內(nèi)容簿晓,這就是安全漏洞的常見原因:

image.png

??注意到一些IDE生成的訪問器會返回對自由數(shù)組字段的引用眶拉,從而導(dǎo)致這個(gè)問題。有兩種方法來解決這個(gè)問題憔儿。你可以使public數(shù)組設(shè)為private忆植,并添加public不可變列表:


image.png

??或者,你可以將數(shù)組設(shè)為private谒臼,添加public方法返回這個(gè)private數(shù)組的副本朝刊。


image.png

??在這些選擇方案中選擇,請考慮客戶端可能會對結(jié)果做什么蜈缤。哪種返回類型更方便拾氓?哪個(gè)會帶來更好的性能?
??從Java9開始底哥,作為模塊系統(tǒng)的一部分引入了兩個(gè)額外的隱式訪問級別咙鞍。模塊是一組包房官,就好像一個(gè)包是一組類一樣。一個(gè)模塊可以在模塊聲明中通過導(dǎo)出聲明顯式導(dǎo)出一些包(按照慣例包含在名為module-info.java的源文件中)续滋,未公開包的public和protected成員在模塊中不受導(dǎo)出聲明的影響翰守,在模塊外無法訪問。使用模塊系統(tǒng)允許你在包中分享類疲酌,而不會對整個(gè)世界可見蜡峰。未導(dǎo)出包的public類的public和protected成員產(chǎn)生兩個(gè)隱式訪問級別,它們是正常公共級別和受保護(hù)級別的模塊內(nèi)類似物徐勃。這種共享的需求相對較少事示,通吃缦瘢可以通過重新安排包中的類可以消除僻肖。
??不像四個(gè)主要訪問級別,這兩個(gè)基于模塊的等級主要是建議性的卢鹦。如果你將模塊的JAR包放在應(yīng)用程序的類路徑上而不是它的模塊路徑上臀脏,模塊中的包重新恢復(fù)非模塊化的行為:包的共用類的所有公共和受保護(hù)成員都具有正常的可訪問性,無論軟件包是否由模塊導(dǎo)出[Reinhold, 1.2]冀自。嚴(yán)格執(zhí)行新引入的訪問級別的地方是JDK本身:Java庫中未導(dǎo)出的包在其模塊之外是真正無法訪問的揉稚。
??對于典型的Java程序員而言,有限的實(shí)用程序模塊不僅提供訪問保護(hù)熬粗,而且主要是咨詢性的搀玖;為了利用它,你必須將你的包分組到模塊中驻呐,在模塊聲明中明確其所有依賴項(xiàng)灌诅,重新排列源代碼樹,并采取特殊操作以適應(yīng)模塊內(nèi)對非模塊化軟件包的任何訪問 [Reinhold, 3].現(xiàn)在說模塊是否會在JDK本身之外得到廣泛使用還為時(shí)過早含末。同時(shí)猜拾,除非你有迫切需要,你最好避免使用它們佣盒。
??總而言之挎袜,你應(yīng)該減少程序元素的可訪問性,盡可能地(在合理范圍內(nèi))肥惭。在仔細(xì)設(shè)計(jì)最小公共API之后盯仪,你應(yīng)該阻止任何雜散類,接口或成員成為API的一部分蜜葱。除了作為常量的公共靜態(tài)final字段之外全景,public類應(yīng)該沒有public字段。確保public static final字段引用的對象是不可變的笼沥。
本文寫于2019.3.19蚪燕,歷時(shí)1天

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末娶牌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子馆纳,更是在濱河造成了極大的恐慌诗良,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鲁驶,死亡現(xiàn)場離奇詭異鉴裹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)钥弯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門径荔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脆霎,你說我怎么就攤上這事总处。” “怎么了睛蛛?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵鹦马,是天一觀的道長。 經(jīng)常有香客問我忆肾,道長荸频,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任客冈,我火速辦了婚禮旭从,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘场仲。我一直安慰自己和悦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布燎窘。 她就那樣靜靜地躺著摹闽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪褐健。 梳的紋絲不亂的頭發(fā)上付鹿,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音蚜迅,去河邊找鬼舵匾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛谁不,可吹牛的內(nèi)容都是我干的坐梯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼刹帕,長吁一口氣:“原來是場噩夢啊……” “哼吵血!你這毒婦竟也來了谎替?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蹋辅,失蹤者是張志新(化名)和其女友劉穎钱贯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侦另,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秩命,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了褒傅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弃锐。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖殿托,靈堂內(nèi)的尸體忽然破棺而出霹菊,到底是詐尸還是另有隱情,我是刑警寧澤碌尔,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布浇辜,位于F島的核電站,受9級特大地震影響唾戚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜待诅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一叹坦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧卑雁,春花似錦募书、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至扣甲,卻和暖如春篮赢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背琉挖。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工启泣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人示辈。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓寥茫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親矾麻。 傳聞我的和親對象是個(gè)殘疾皇子纱耻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容