本文大多數(shù)內(nèi)容適用于構(gòu)造器吭练,也適用于普通方法诫龙,焦點(diǎn)集中在可用性析显、健壯性和靈活性上鲫咽。
1.檢查參數(shù)的有效性
一個原則:應(yīng)該在發(fā)生錯誤之后盡快檢測出錯誤
不檢查參數(shù)有效性的后果:
- 方法在處理的過程中失敗签赃,并且產(chǎn)生令人費(fèi)解的異常
- 方法正常返回,但計算出錯誤的結(jié)果
- 方法正常返回分尸,但卻使得某個對象處于被破壞的狀態(tài)锦聊,將來在某個不確定的時候引發(fā)錯誤
常見方法參數(shù)的一些限制:
- 索引值必須是非負(fù)數(shù)
- 集合類的索引不能大于集合長度-1
- 對象引用不能為 null
public 方法參數(shù)有效性檢查
步驟:
- 用 Javadoc 的
@throw
標(biāo)簽在文檔中說明違法參數(shù)值限制時拋出的異常,異常通常為 IllegalArgumentException箩绍、IndexOutOfBoundsException孔庭、NullPointerException等 - 公有方法內(nèi)部進(jìn)行參數(shù)有效性檢查,并拋出相應(yīng)異常
/**
* Returns a BigInteger whose value is.
* @param m
* @return this mod m
* @throws ArithmeticException if m is less than or equal to 0
*/
public BigInteger mod(BigInteger m) {
if (m.signum() <= 0) {
throw new ArithmeticException("Modulus <= 0:" + m);
}
// Do the computation
}
private/package-private 方法參數(shù)有效性檢查
非公有方法通常應(yīng)該使用斷言來檢查它們的參數(shù)有效性
斷言與普通有效性檢查的區(qū)別:
- 斷言如果失敗材蛛,拋出 AssertionError
- 如果它們沒起到作用圆到,本質(zhì)上也不會有成本開銷
private static void sort(long a[], int offset, int length) {
assert a != null;
assert offset >= 0 && offset <= a.length;
assert length >= 0 && length <= a.length - offset;
// next
}
構(gòu)造方法參數(shù)有效性檢查
對于有些參數(shù),方法本身沒有用到卑吭,但被保存起來供以后使用芽淡。比如靜態(tài)工廠方法、構(gòu)造方法豆赏。檢查參數(shù)有效性非常重要挣菲,避免構(gòu)造出來的對象違反了這個類的約束條件
不需要檢查參數(shù)有效性的情況
- 有效性檢查很昂貴,或者不切實際贺奠,比如
sort
方法谁尸,檢查集合中每一個對象是否可以比較 - 計算會隱式執(zhí)行必要的有效性檢查陈醒,比如
sort
方法,如果對象不能比較或杠,就會拋出 ClassCastException
總結(jié)
- 編寫方法前,考慮好它的參數(shù)有哪些限制
- 把限制寫到方法開頭的文檔中
- 通過顯式的檢查來實施限制
2.必要時進(jìn)行保護(hù)性拷貝
先介紹一個概念宣蔚,不可變性廷痘,之前介紹過,要盡可能創(chuàng)建不可變的類件已,因為它有很多優(yōu)點(diǎn)笋额,其中創(chuàng)建不可變類有幾條規(guī)則:
- 如果類具有指向可變對象的域,必須確保該類的客戶端無法獲得執(zhí)行這些對象的引用
- 在構(gòu)造器中篷扩,永遠(yuǎn)不要用客戶端提供的對象引用來初始化這樣的域
- 在訪問方法中兄猩,也不要返回該對象引用
- 在構(gòu)造器、訪問方法和 readObject 方法中請使用保護(hù)性拷貝技術(shù)
創(chuàng)建 Period 類鉴未,由于Date類是可變的枢冤,所以外部可能拿到內(nèi)部的 start 和 end 信息,進(jìn)而會修改這個信息
public final class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
this.start = start;
this.end = end;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
}
對于構(gòu)造器的每個可變參數(shù)進(jìn)行保護(hù)性拷貝
為了避免內(nèi)部信息被攻擊铜秆,對于構(gòu)造器的每個可變參數(shù)進(jìn)行保護(hù)性拷貝淹真,創(chuàng)建新的對象,而不是使用客戶端傳入的對象
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
}
注意:
- 保護(hù)性拷貝是在檢查參數(shù)有效性之前進(jìn)行
- 保護(hù)性拷貝连茧,沒有使用
clone
方法核蘸,因為 Date 是非 final 的巍糯,clone 方法不能保證一定會返回 Date 對象,可能返回出于惡意目的而設(shè)計的不可信子類的實例
使訪問方法返回可變內(nèi)部域進(jìn)行保護(hù)性拷貝
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
總結(jié)
- 參數(shù)(和返回值)的保護(hù)性拷貝不僅僅針對不可變類客扎,每當(dāng)允許客戶提供的對象進(jìn)入內(nèi)部數(shù)據(jù)結(jié)構(gòu)中祟峦,則有必要考慮一下,客戶提供的對象是否有可能是變化的
- 長度非零的數(shù)組總是可變的徙鱼,在把內(nèi)部數(shù)組返回給客戶端時宅楞,總要進(jìn)行保護(hù)性拷貝
- 真正的啟示:盡可能使用不可變對象,不必再擔(dān)心保護(hù)性拷貝
- 對于Date袱吆,通常不要直接使用Date的引用厌衙,而是使用Date.getTime()返回的long基本類型作為時間的表示,防止Date對象的可變性導(dǎo)致的問題
最后绞绒,如果類具有從客戶端得到或者返回到客戶端的可變組件迅箩,類就必須保護(hù)性地拷貝這些組件,如果拷貝的成本受到限制处铛,并且類信任它的客戶端不會不恰當(dāng)?shù)匦薷慕M件饲趋,就可以在文檔中指明客戶端的職責(zé)是不得修改受到影響的組件,以此來代替保護(hù)性拷貝撤蟆。
3.謹(jǐn)慎設(shè)計方法簽名
API 設(shè)計技巧總結(jié):
- 謹(jǐn)慎地選擇方法的名稱
- 易于理解
- 與同一個包中的其他名稱風(fēng)格一致的名稱
- 選擇與大眾認(rèn)可的名稱
- 不要過于追求提供便利的方法
- 避免過長的參數(shù)列表
- 四個參數(shù)或更少
縮短長參數(shù)列表的方式:
- 把方法分解成多個方法奕塑,每個方法只需要這些參數(shù)的一個子集
- 創(chuàng)建輔助類,用來保存參數(shù)的分組家肯,一般是靜態(tài)成員類龄砰,如果一個頻繁出現(xiàn)的參數(shù)序列可以被看作代表了某個獨(dú)特實體,則建議使用這種方式
- 使用Builder模式讨衣,參數(shù)很多换棚,且有些是可選的。
類參數(shù)的使用技巧:
- 對于參數(shù)類型反镇,要優(yōu)先使用接口而不是類
- 比如固蚤,沒有理由使用 HashMap 作為參數(shù),應(yīng)當(dāng)使用 Map 接口作為參數(shù)
- 對于 boolean 參數(shù)歹茶,要優(yōu)先使用兩個元素的枚舉類型
- 代碼更易于閱讀和編寫
4.慎用重載
重載方法的選擇是靜態(tài)的夕玩,是在編譯時做出決定的,只能調(diào)用與此明確對應(yīng)的重載方法惊豺,而不是其父類或者子類燎孟。與此不同的是覆蓋方法,覆蓋方法是動態(tài)的尸昧,是在運(yùn)行時決定要調(diào)用子類還是父類的方法揩页。
看一個例子。
private class CollectionClassifier {
public static String classify(Set<?> s) {
return "Set";
}
public static String classify(List<?> s) {
return "List";
}
public static String classify(Collection<?> s) {
return "Unknown Collection";
}
public static void main(String[] args) {
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<BigInteger>(),
new HashMap<String, String>().values()
};
for (Collection<?> c : collections) {
System.out.println(classify(c));
}
}
}
結(jié)果烹俗,打印了三次 “Unknown Collection”爆侣,對于for循環(huán)的三次迭代萍程,只能調(diào)用在編譯時確定的參數(shù)為 Collection<?> 的方法。
解決方案:用單個方法替換三個重載方法
public static String classify(Collection<?> c) {
return c instanceof Set ? "Set" :
c instanceof List ? "List" : "Unknown Collection";
}
普通方法避免重載
具體累提,對于write方法,如果就有變形磁浇,不應(yīng)該使用重載斋陪,而是增加諸如writeBoolean、writeInte這樣的簽名方法置吓。
構(gòu)造器方法避免重載
不能使用不同名稱的構(gòu)造器无虚,但可以選擇導(dǎo)出靜態(tài)工廠,而不是重載構(gòu)造器
總結(jié)
“能夠重載方法”并不意味著“應(yīng)該重載方法”衍锚。一般情況下友题,對于多個具有相同參數(shù)數(shù)目的方法來說,應(yīng)該盡量避免重載方法戴质。
5.慎用可變參數(shù)
可變參數(shù):可變參數(shù)方法接口0個或者多個指定類型的參數(shù)度宦。可變參數(shù)機(jī)制通過先創(chuàng)建一個數(shù)組告匠,數(shù)組的大小為調(diào)用位置所傳遞的參數(shù)數(shù)量戈抄,然后將參數(shù)值傳到數(shù)組中,最后將數(shù)組傳遞給方法后专。
可變參數(shù)性能問題:可變參數(shù)方法的每次調(diào)用功能會導(dǎo)致進(jìn)行一次數(shù)組分配和初始化划鸽。
只有對于參數(shù)數(shù)目不確定的情況,才會使用可變參數(shù)戚哎。
6.返回零長度的數(shù)組或者集合裸诽,而不是 null
對于一個返回null而不是零長度的數(shù)組或者集合的方法,編寫客戶端的程序員很可能會忘記這種專門的代碼來處理null返回值型凳。
返回零長度數(shù)組不會增加開銷丈冬,零長度數(shù)組是不可變的,是自由共享的甘畅。