Effective Java(3rd)-Item50 在需要的時候制作防御性的副本

??Java是一種安全的語言添诉,這是它的一大優(yōu)點挑社。這意味著在沒有本機方法的情況下,它不受緩沖區(qū)溢出顿痪、數(shù)組溢出镊辕、野生指針和其他內存損壞錯誤的影響油够,這些錯誤困擾著C和c++等不安全語言。在一種安全的語言中丑蛤,可以編寫類并確定它們的不變量將保持不變吁系,不管在系統(tǒng)的任何其他部分發(fā)生了什么汉矿。在將所有內存視為一個巨大數(shù)組的語言中,這是不可能的动猬。
??即使使用一種安全的語言虏束,如果您不付出一些努力棉饶,也無法與其他類隔離。您必須進行防御性的編程镇匀,并假定您的類的客戶端會盡最大努力破壞它的不變量照藻。隨著人們更加努力地破壞系統(tǒng)的安全性,這一點越來越正確汗侵,但更常見的情況是幸缕,您的類將不得不處理由善意程序員的誠實錯誤所導致的意外行為。無論哪種方式晰韵,都值得花時間編寫面對行為不端的客戶端的健壯類发乔。
??雖然如果沒有對象的幫助,另一個類是不可能修改對象的內部狀態(tài)的雪猪,但是提供這樣的幫助卻出奇地容易栏尚。例如,考慮下面的類只恨,它聲稱表示一個不可變的時間段:

image.png

image.png

??乍一看译仗,這個類似乎是不可變的,并且強制一個周期的開始不跟隨它的結束官觅。然而纵菌,利用日期是可變的這一事實很容易違反這個不變量:


image.png

??從Java 8開始,解決這個問題的明顯方法就是使用Instant(或LocalDateTime或ZonedDateTime)代替Date休涤,因為Instant(和其他Java.time 類)是不可變的( item17 )咱圆。Date已過時,不應在新代碼中使用滑绒。盡管如此闷堡,問題仍然存在:有時您必須在api和內部表示中使用可變值類型,本項目中討論的技術適用于這些時候疑故。

??為了保護Period實例的內部不受這種攻擊杠览,必須將每個可變參數(shù)的防御性副本復制到構造函數(shù)并使用副本代替正本,作為期間實例的組成部分:


image.png

?? 有了新的構造函數(shù)纵势,之前的攻擊將不會對Period實例產生影響踱阿。注意到防御副本在檢查參數(shù)有效性(item49)前制作管钳,對副本而不是原件進行有效性檢查。雖然這看起來不自然软舌,但卻是必要的才漆。它保護類不受其他線程參數(shù)更改的影響在漏洞窗口期間,從檢查參數(shù)到復制參數(shù)的時間間隔佛点。在計算機安全社區(qū)醇滥,這被稱為檢查時間/使用時間或TOCTOU攻擊[Viega01].

??還要注意,我們沒有使用Date的clone方法來創(chuàng)建防御性副本超营。因為Date是非final的鸳玩,所以不能保證克隆方法返回一個類為java.util.Date的對象:它可以返回一個不受信任子類的實例,這個子類是專門為惡意破壞而設計的演闭。例如不跟,這樣的子類可以在創(chuàng)建時在私有靜態(tài)列表中記錄對每個實例的引用,并允許攻擊者訪問這個列表米碰。這將使攻擊者可以自由控制所有實例窝革。為了防止這種攻擊,不要使用克隆方法對類型可由不受信任方子類化的參數(shù)進行防御性復制吕座。
??雖然替換構造函數(shù)成功地防御了之前的攻擊虐译,但是仍然可以修改Period實例,因為它的訪問器提供了對其可變內部結構的訪問:

image.png

??要防御第二次攻擊米诉,只需修改訪問器菱蔬,返回可變內部字段的防御副本:

image.png

??有了新的構造函數(shù)和新的訪問器,Period實際上是不可變的史侣。無論程序員多么惡意或無能拴泌,都不可能違背period 的開始和結束不一致這一不變式(不借助語言以外的手段,如native方法和反射).這是真的惊橱,因為除了Period本身之外蚪腐,任何類都無法訪問Period實例中的任何可變字段。這些字段真正封裝在對象中税朴。
??在訪問器中回季,與構造函數(shù)不同,可以使用clone方法進行防御性復制正林。這是因為我們知道Period的內部Date對象的類是java.util.Date泡一,而不是某個不可信的子類。也就是說觅廓,出于第13項中列出的原因鼻忠,通常最好使用構造函數(shù)或靜態(tài)工廠來復制實例。

??參數(shù)的防御性復制不僅適用于不可變類杈绸。在編寫方法或構造函數(shù)時帖蔓,如果要在內部數(shù)據(jù)結構中存儲對客戶機提供的對象的引用矮瘟,請考慮客戶機提供的對象是否可能是可變的。如果是塑娇,請考慮在對象進入數(shù)據(jù)結構之后澈侠,您的類是否能夠容忍對象中的更改。如果答案是否定的埋酬,則必須防御性地復制對象哨啃,并將副本輸入到數(shù)據(jù)結構中,而不是原始結構中奇瘦。舉個例子,如果你正在考慮使用一個對象引用作為一個具備此元素在內部設置實例或作為一個關鍵的內部地圖實例,您應該意識到的不變量設置或地圖會損壞如果對象被修改后插入棘催。
??在將內部組件返回給客戶端之前對其進行防御性復制也是如此劲弦。無論您的類是否是不可變的耳标,在返回對可變內部組件的引用之前,您都應該三思邑跪。很有可能次坡,您應該返回一個防御性副本。記住画畅,非零長度數(shù)組總是可變的砸琅。因此,在將內部數(shù)組返回給客戶機之前轴踱,應該始終創(chuàng)建一個防御性的副本症脂。或者淫僻,您可以返回數(shù)組的不可變視圖诱篷。這兩種技術都顯示在項目15中。
??可以說雳灵,所有這些的真正教訓是棕所,在可能的情況下,應該使用不可變對象作為對象的組件悯辙,這樣就不必擔心防御性復制(item17)琳省。在我們的Period示例中,使用Instant(或LocalDateTime或ZonedDateTime)躲撰,除非您使用的是Java 8之前的版本针贬。如果使用較早的版本,一個選項是存儲Date. gettime()返回的long原語拢蛋,而不是Date引用桦他。

??可以說,所有這些的真正教訓是瓤狐,在可能的情況下瞬铸,應該使用不可變對象作為對象的組件批幌,這樣就不必擔心防御性復制(item17)。在我們的Period示例中嗓节,使用Instant(或LocalDateTime或ZonedDateTime)荧缘,除非您使用的是Java 8之前的版本。如果使用較早的版本拦宣,一個選項是存儲Date. gettime()返回的long原語截粗,而不是Date引用。如果一個類信任它的調用者不修改內部組件鸵隧,可能是因為類和它的客戶端都是同一個包的一部分绸罗,那么就應該避免防御性復制。在這種情況下豆瘫,類文檔應該表明調用者不能修改受影響的參數(shù)或返回值珊蟀。

??即使跨越包邊界,在將可變參數(shù)集成到對象之前對其進行防御性復制也并不總是合適的外驱。有一些方法和構造函數(shù)育灸,它們的調用指示參數(shù)引用的對象的顯式切換。當調用這樣一個方法時昵宇,客戶端承諾不再直接修改對象磅崭。希望擁有客戶機提供的可變對象所有權的方法或構造函數(shù)必須在其文檔中明確說明這一點。
??包含方法或構造函數(shù)的類瓦哎,這些方法或構造函數(shù)的調用指示控制權的轉移砸喻,不能保護自己免受惡意客戶機的攻擊。只有當一個類和它的客戶機之間存在相互信任蒋譬,或者對類的不變量的破壞只會對客戶機造成傷害時割岛,這樣的類才是可接受的。后一種情況的一個例子是包裝器類模式(item18 )羡铲。根據(jù)包裝器類的性質蜂桶,客戶機可以在包裝對象之后直接訪問對象,從而破壞類的不變量也切,但這通常只會損害客戶機扑媚。

??總而言之,如果一個類具有從客戶端獲取或返回給客戶端的可變組件雷恃,則該類必須防御性地復制這些組件疆股。如果復制的成本過高,并且類信任它的客戶端不會不適當?shù)匦薷慕M件倒槐,那么防御性的復制可能被概述客戶端不修改受影響組件的責任的文檔所取代旬痹。
本文寫于2019.7.17,歷時1天

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市两残,隨后出現(xiàn)的幾起案子永毅,更是在濱河造成了極大的恐慌,老刑警劉巖人弓,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沼死,死亡現(xiàn)場離奇詭異,居然都是意外死亡崔赌,警方通過查閱死者的電腦和手機意蛀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來健芭,“玉大人县钥,你說我怎么就攤上這事〈嚷酰” “怎么了若贮?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吩翻。 經(jīng)常有香客問我兜看,道長,這世上最難降的妖魔是什么狭瞎? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮搏予,結果婚禮上熊锭,老公的妹妹穿的比我還像新娘。我一直安慰自己雪侥,他們只是感情好碗殷,可當我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著速缨,像睡著了一般锌妻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旬牲,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天仿粹,我揣著相機與錄音,去河邊找鬼原茅。 笑死吭历,一個胖子當著我的面吹牛,可吹牛的內容都是我干的擂橘。 我是一名探鬼主播晌区,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了朗若?” 一聲冷哼從身側響起恼五,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哭懈,沒想到半個月后唤冈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡银伟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年你虹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彤避。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡傅物,死狀恐怖,靈堂內的尸體忽然破棺而出琉预,到底是詐尸還是另有隱情董饰,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布圆米,位于F島的核電站卒暂,受9級特大地震影響,放射性物質發(fā)生泄漏娄帖。R本人自食惡果不足惜也祠,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望近速。 院中可真熱鬧诈嘿,春花似錦、人聲如沸削葱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽析砸。三九已至昔字,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間首繁,已是汗流浹背作郭。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛮瞄,地道東北人所坯。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像挂捅,于是被迫代替她去往敵國和親芹助。 傳聞我的和親對象是個殘疾皇子堂湖,可洞房花燭夜當晚...
    茶點故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內容

  • [{"reportDate": "2018-01-23 23:28:49","fluctuateCause": n...
    加勒比海帶_4bbc閱讀 768評論 1 2
  • 目錄: Android:Android 0.*Android 1.*Android 2.*Android 3.*A...
    敲代碼的令狐蔥閱讀 3,905評論 0 2
  • 一曲長歌欲斷腸, 離別巷状土, 細雨落千殤 歸去兮 舉杯空對影 獨酌 淚兩行 來時望 一枝紅杏獨出墻 從此鴻雁無處往 ...
    秋愚閱讀 397評論 0 0
  • 他是時光轉換中為后世遺失的男子无蜂,卻是我裴衾此一生不能忘的人。他名徐等閑蒙谓。飄飖若風的徐斥季,舊年曾識春風面的等閑。 到頭...
    懷嵐閱讀 982評論 0 0
  • Ubuntu 16.04 LTS install oracle 11g jdk環(huán)境沒驗證累驮,反正是裝了 1.user...
    米開朗基樂閱讀 633評論 0 1