聲明:原創(chuàng)作品,轉(zhuǎn)載時(shí)請(qǐng)注明文章來(lái)自SAP師太技術(shù)博客( 博/客/園www.cnblogs.com):m.survivalescaperooms.com/jiangzhengjun,并以超鏈接形式標(biāo)明文章原始出處,否則將追究法律責(zé)任!原文鏈接:http://m.survivalescaperooms.com/jiangzhengjun/p/4255671.html 第七章 方法38、 檢查參數(shù)的有效性絕大多數(shù)方法和構(gòu)造器對(duì)于傳遞給它們的參數(shù)值都會(huì)有某些限制。例如,索引值必須是非負(fù)的,對(duì)象引用不能為null等,這些都是常見的。你應(yīng)該在文檔中清楚地指明所有這些限制,并且在方法體的開頭處檢查參數(shù),以強(qiáng)制施加這些限制。
應(yīng)該在方法和構(gòu)造器體前進(jìn)行了參數(shù)的有效性檢查,并且及時(shí)向外拋出適當(dāng)?shù)漠惓!H绻椒](méi)有檢查它的參數(shù),就有可能發(fā)生幾種情形。該方法可能在處理過(guò)程中失敗,并且產(chǎn)生令人費(fèi)解的異常,更有可能,該方法可以正常返回,但是會(huì)悄悄地計(jì)算出錯(cuò)誤的結(jié)果。
對(duì)于公有的方法,要用JavaDoc的@throws標(biāo)簽(tag)在文檔中說(shuō)明違反參數(shù)值限制時(shí)會(huì)拋出的異常(見第62條),這樣的異常通常為IllegalArgumentException、IndexOutOfBoundsException或NullPionterException(見第60條),下面是一個(gè)例子:
/**
* ...
*@paramm m為系數(shù),必須是正數(shù)
*@return this mod m
*@throws如果m小于等于0時(shí)拋出ArithmeticException
*/
publicBigInteger mod(BigInteger m) {
if(m.signum() <= 0) {
thrownewArithmeticException("Modulus <= 0:" + m);
}
...// do the computations
}
對(duì)于那此非公有方法,你可以控制這個(gè)方法在哪些情況下被調(diào)用,因此你可以,也應(yīng)該確保只將有效的參數(shù)值傳遞進(jìn)來(lái)。因此,非公有的方法通常應(yīng)該使用斷言來(lái)檢查它們的參數(shù),具體做法如下:
PRivatestaticvoidsort(longa[],intoffset,intlength) {
asserta !=null;
assertoffset >= 0 && offset <= a.length;
assertlength >= 0 && length <= a.length - offset;
// ... do the computation
}
斷言就是被斷言的條件將會(huì)為真,否則將會(huì)拋出AssertionError。不同于一般的有效性檢查,如果它們沒(méi)有起到作用,本質(zhì)上也不會(huì)有成本開銷,除非通過(guò)將–ea(或者-enableassertions)開關(guān)傳遞給Java解釋器,來(lái)啟用它們。
對(duì)于有些參數(shù),方法本身沒(méi)有用到,卻被保存起來(lái)供以后使用,則檢驗(yàn)這類參數(shù)的有效性尤其重要,因?yàn)閷?lái)在使用時(shí)拋出異常時(shí)要想找到這個(gè)參數(shù)的來(lái)源就非常困難了,所以這類參數(shù)更應(yīng)該先檢查,這個(gè)規(guī)則也適用于構(gòu)造函數(shù)。
不是所有的參數(shù)都就應(yīng)該在使用前檢查他的有效性,因?yàn)橛械臋z查是要代價(jià)的,或根本是不切實(shí)際的,而且有效性檢查本身就可以在計(jì)算過(guò)程中完成。例如,Collections.sort(List)對(duì)集合排序的方法,集合中的每個(gè)元素都實(shí)現(xiàn)了Comparable接口,否則在計(jì)算時(shí)會(huì)拋出ClassCastException,這正是sort方法所應(yīng)該做的事情,因此,提前檢測(cè)是否實(shí)現(xiàn)了該比較接口是沒(méi)有多大意義的,再說(shuō)多遍歷一次也是消耗性能的,但此時(shí)我們先不進(jìn)行參數(shù)有效性檢測(cè),則無(wú)效的參數(shù)值會(huì)導(dǎo)致計(jì)算過(guò)程拋出異常,而這種異常與文檔中標(biāo)明這個(gè)方法拋出的異常不符,在這種情況下,我們應(yīng)該轉(zhuǎn)換異常,將計(jì)算過(guò)程中拋出的異常轉(zhuǎn)換為正確的異常。
當(dāng)然,不是要求所有的參數(shù)都要求檢查的,有些參數(shù)在實(shí)際應(yīng)用中不會(huì)產(chǎn)生非法值或在我們需范圍內(nèi)不會(huì)有其他值,則就不需要進(jìn)行檢測(cè)。
39、 必要時(shí)進(jìn)行保護(hù)性拷貝保護(hù)性拷貝的實(shí)例:
publicfinalclassPeriod {
privatefinalDate start;
privatefinalDate end;
publicPeriod(Date start, Date end) {
this.start =newDate(start.getTime());
this.end =newDate(end.getTime());
//開始時(shí)間一定不能大于結(jié)束時(shí)間
if(this.start.compareTo(this.end) > 0)
thrownewIllegalArgumentException(start + " after " + end);
}
publicDate start() {
return(Date)start.clone();
//returnnewDate(start.getTime());
}
publicDate end() {
return(Date)end.clone();
//returnnewDate(end.getTime());
}
}
這里兩個(gè)地方都需要進(jìn)行保護(hù)性拷貝,第一個(gè)是進(jìn)參的地方,這里是構(gòu)造器;第二個(gè)地方是傳出去的地方,這里為start與end方法,如果丟掉一個(gè)地方,都有可能打破上面約束條件“開始時(shí)間一定不大于結(jié)束時(shí)間”。注,在構(gòu)造器中,保護(hù)性拷貝是在檢查參數(shù)的有效性之前進(jìn)行的,并且有效性檢查是針對(duì)拷貝之后的對(duì)象,而不是針對(duì)原始對(duì)象,這是一定要這么做的,因?yàn)槿绻麑z測(cè)放在了最前面,則當(dāng)檢測(cè)滿足條件后另一線程改變了原始對(duì)象參數(shù)的值,此進(jìn)檢測(cè)實(shí)質(zhì)上已無(wú)效,所以這里與第38條并不是矛盾的。
同時(shí)請(qǐng)注意,構(gòu)造器中我們沒(méi)有用Date的clone方法來(lái)進(jìn)行保護(hù)性拷貝。因?yàn)镈ate是非final類,不能保證傳進(jìn)來(lái)的一點(diǎn)是Date類型對(duì)象:它有可能是專門出于惡意的目的而設(shè)計(jì)的不可信子類的實(shí)例,這樣我們調(diào)用clone方法時(shí)實(shí)質(zhì)上不是調(diào)用Date上面的clone方法,而是惡意子對(duì)象上的,這樣在克隆時(shí)子類可以在每個(gè)克隆實(shí)例被創(chuàng)建的時(shí)候,把指向該實(shí)例的引用記錄到一個(gè)私有的靜態(tài)列表中,并且允許攻擊者訪問(wèn)這個(gè)列表,這將使得攻擊者可以自由地控制所有的實(shí)例。所以,對(duì)于傳進(jìn)的參數(shù)實(shí)例我不要使用clone方法進(jìn)行保護(hù)拷貝,而是直接使用new的創(chuàng)建方式來(lái)拷貝。
然而,對(duì)于訪問(wèn)方法,與構(gòu)造器不同,在進(jìn)行保護(hù)拷貝時(shí)候是允許使用clone方法的。之所以可以,是因?yàn)槲覀冎溃琍eriod內(nèi)部的Date對(duì)象的類是java.util.Date,而不可能是其他某個(gè)潛在的不可信子類。
參數(shù)的保護(hù)性拷貝并不僅僅針對(duì)不可變類。每當(dāng)編寫方法或者構(gòu)造器時(shí),如果它要允許客戶提供的對(duì)象進(jìn)入到內(nèi)部數(shù)據(jù)結(jié)構(gòu)中,則有必要考慮一下,客戶提供的對(duì)象是否有可能是可變的。如果是,就要考慮你的類是否能容忍對(duì)象進(jìn)入數(shù)據(jù)結(jié)構(gòu)之后發(fā)生變化,如果不允許,就必須對(duì)該對(duì)象進(jìn)行保護(hù)性拷貝,以防止相互影響。
在內(nèi)部組件返回給客戶端之前,對(duì)它們進(jìn)行保護(hù)性拷貝也是同樣的道理。不管類是否為不可變的,在把一個(gè)指向內(nèi)部可變組件的引用返回給客戶端之前,也應(yīng)該考慮是否進(jìn)行拷貝。
記住長(zhǎng)度非零的數(shù)組總是可變的。因引,在把內(nèi)部數(shù)組返回給客戶端之前,應(yīng)該總要進(jìn)行保護(hù)性拷貝。另一種解決方案是,給客戶端返回該數(shù)組的不可變視圖,這兩種方法在第13條中已演示過(guò)了。
保護(hù)性拷貝可能會(huì)帶來(lái)相關(guān)的性能損失,如果類信任它的調(diào)用都不會(huì)修改內(nèi)部的組件,可能因?yàn)轭惣捌淇蛻舳硕际峭粋€(gè)包的雙方,那么不進(jìn)行保護(hù)性拷貝也是可以的。在這種情況下,類的文檔必須清楚地說(shuō)明,調(diào)用者絕不能修改受影響的參數(shù)或者返回值。
總之,如果類具有從客戶端得到或者返回到客戶端的可變組件,類就必須保護(hù)性地拷貝這些組件。如果拷貝的成本受到限制,并且類信任它的客戶端不會(huì)不恰當(dāng)?shù)匦薷倪@些組件,就可以在文檔中指明不能修改這些受到影響的組件,以此來(lái)代替保護(hù)性拷貝。
40、 謹(jǐn)慎設(shè)計(jì)方法簽名1、謹(jǐn)慎地選擇方法的名稱。遵循標(biāo)準(zhǔn)命名習(xí)慣,風(fēng)格統(tǒng)一、大眾認(rèn)可的相一致的名稱。設(shè)計(jì)時(shí)可以參考類庫(kù)。
2、不要過(guò)于追求提供便利的方法。每個(gè)方法都應(yīng)該盡其所能。方法太多會(huì)使類難以學(xué)習(xí)、使用、文檔化、測(cè)試和維護(hù)。對(duì)于接口,更是這樣,方法太多會(huì)使用接口實(shí)現(xiàn)者和接口用戶的工作變得復(fù)雜起來(lái)。對(duì)于類和接口所支持的每個(gè)動(dòng)作,都提供一個(gè)功能齊全的方法。只有當(dāng)一項(xiàng)操作被經(jīng)常用到時(shí),考慮為它提供快捷的方式。
3、避免過(guò)長(zhǎng)的參數(shù)列表。目標(biāo)是四個(gè)參數(shù)或者更少。相同類型的長(zhǎng)參數(shù)序列格外有害,如果不小心弄錯(cuò)了參數(shù)順序時(shí),他們的程序仍然可以編譯和運(yùn)行。有三種方法可以縮短過(guò)長(zhǎng)的參數(shù)列表。第一種是把方法分解成多個(gè)方法,每個(gè)方法只需要這些參數(shù)的一個(gè)子集,即將多功能分解成多個(gè)單一的小功能。第二種方法是創(chuàng)建輔助類,如果使用FromBean封裝了頁(yè)面上的所有參數(shù)然后傳到Action。第三種是結(jié)合了前兩種方法特征,從對(duì)象構(gòu)造到方法調(diào)用都采用Builder模式(參見第2條),如果方法有多個(gè)參數(shù),其中有些又是可選的,最好定義一個(gè)對(duì)象來(lái)表示所有參數(shù),并允許客戶端在這個(gè)對(duì)象上進(jìn)行多次“setter”調(diào)用,每次調(diào)用都設(shè)置一個(gè)參數(shù)或設(shè)置一個(gè)較小的集合,一旦設(shè)置了所需要的參數(shù),客戶端就調(diào)用這個(gè)對(duì)象的“執(zhí)行(execute)”方法,它對(duì)參數(shù)進(jìn)行最終的有效性檢查,并執(zhí)行實(shí)際的計(jì)算。
對(duì)于參數(shù)傳遞類型,我們要優(yōu)先使用接口而不是類(請(qǐng)見第52條),只要有適當(dāng)?shù)慕涌诳捎脕?lái)定義參數(shù),就優(yōu)先使用這個(gè)接口,而不是這個(gè)接口實(shí)現(xiàn)。我們沒(méi)有理由在編寫方法時(shí)使用HashMap類來(lái)作為輸入,相反,應(yīng)當(dāng)使用Map接口作為參數(shù)類型,這使你可以傳進(jìn)各種Map的實(shí)現(xiàn),如果碰巧輸入的數(shù)據(jù)是以其他形式存在,使用具體類類型作為參數(shù)時(shí)就會(huì)導(dǎo)致不必要的轉(zhuǎn)換操作。
對(duì)于boolean參數(shù),要優(yōu)先使用兩個(gè)元素的枚舉類型,這樣便于以后第種選擇的加入,這樣不必要再添加另一個(gè)方法。
41、 慎用重載publicclassCollectionClassifier {
publicstaticString classify(Set<?> s) {
return"Set";
}
publicstaticString classify(List<?> lst) {
return"List";
}
publicstaticString classify(Collection<?> c) {
return"Unknown Collection";
}
publicstaticvoidmain(String[] args) {
Collection<?>[] collections = {newHashSet<String>(),
newArrayList<BigInteger>(),newHashMap<String, String>().values() };
for(Collection<?> c : collections)
System.out.println(classify(c));
}
}
Word-spacing: 0px; text-transform: none; word-break: normal; margin: 0cm 0cm 0
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注