這一章介紹方法設(shè)計(jì)的幾個(gè)方面:如何對(duì)待參數(shù)和返回值,如何設(shè)計(jì)方法簽名,如何注釋方法
Item38: 檢查參數(shù)的合法性
大部分使用的方法參數(shù)都有一定的限制,如不為null,size>0等
通用的原則就是預(yù)防大于整改,提前發(fā)現(xiàn)錯(cuò)誤可以更快的規(guī)避問(wèn)題,而不是在程序運(yùn)行中發(fā)生
對(duì)于公共方法高蜂,使用Javadoc@塊標(biāo)記,來(lái)記錄在違反參數(shù)值限制時(shí)拋出的異常(Item62)舵盈。
/**
* Returns a BigInteger whose value is (this mod m). This method
* differs from the remainder method in that it always returns a
* non-negative BigInteger.
*
* @param m the modulus, which must be positive
* @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
}
對(duì)于私有的方法則使用斷言
// Private helper function for a recursive sort
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;
... // Do the computation
}
assert在實(shí)際項(xiàng)目中使用的很少,更多的還是使用的if判斷
每次寫(xiě)一個(gè)方法或者構(gòu)造函數(shù)的時(shí)候,在方法的開(kāi)頭考慮參數(shù)的合法性是必不可少的
Item39: 必要的時(shí)候使用拷貝對(duì)象
Java是一門(mén)安全的語(yǔ)言,即使在Java語(yǔ)言中,也要假設(shè)用戶正想方設(shè)法的破壞你的程序.除了少部分人想破壞系統(tǒng)的安全性,大部分問(wèn)題都是編程人員可以控制的
雖然沒(méi)有對(duì)象的幫助,另一個(gè)類(lèi)不太可能改變對(duì)象的內(nèi)部狀態(tài),但偶爾也有疏忽的地方
//一個(gè)不可變的周期類(lèi)
// Broken "immutable" time period class
public final class Period {
private final Date start;
private final Date end;
/**
* @param start the beginning of the period
* @param end the end of the period; must not precede start
* @throws IllegalArgumentException if start is after end
* @throws NullPointerException if start or end is null
*/
public Period(Date start, Date end) {
if (start.compareTo(end) > 0)
throw new IllegalArgumentException(
start + " after " + end);
this.start = start;
this.end = end;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
... // Remainder omitted
}
其中Date對(duì)象是可變的
// bad
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // 修改了p的內(nèi)部狀態(tài)
為了保護(hù)對(duì)象的狀態(tài),我們需要在構(gòu)造函數(shù)中對(duì)可變參數(shù)執(zhí)行拷貝防御
// Repaired constructor - makes defensive copies of parameters
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0)
throw new IllegalArgumentException(start +" after "+ end);
}
// Repaired accessors - make defensive copies of internal fields
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
這里沒(méi)有使用clone方法,因?yàn)镈ate有其它不可信任的子類(lèi)
經(jīng)驗(yàn)上講,在內(nèi)部盡量不要使用可變的類(lèi),如Date,可用long型替代Date.getTime()
總結(jié):如果一個(gè)類(lèi)調(diào)用了或返回可變的對(duì)象,則需要用拷貝對(duì)象防御,如果很信任調(diào)用者不會(huì)修改類(lèi)的內(nèi)部狀態(tài),則需要有一份警告文檔提示調(diào)用者不能修改類(lèi)的狀態(tài)
Item 40: 如何設(shè)計(jì)方法
- 選擇一個(gè)合適的方法名稱(chēng)
你的主要目標(biāo)是設(shè)計(jì)一個(gè)利于理解的方法名,次要目標(biāo)是方法名稱(chēng)之間保持協(xié)調(diào)性,如向數(shù)據(jù)庫(kù)中插入一條數(shù)據(jù),有的使用addXX,有的使用setXX,有的使用insertXX,盡量保持統(tǒng)一 - 不要過(guò)分的使用方法
太多的方法容易使一個(gè)類(lèi)難于維護(hù)和測(cè)試,只要當(dāng)它需要經(jīng)常調(diào)用的時(shí)候才考慮提出一個(gè)方法,否則就不管它 - 避免參數(shù)長(zhǎng)的方法
盡量保持在四個(gè)參數(shù)或以下
有三種方式避免長(zhǎng)參數(shù)
1.提出更多的方法
2.使用輔助類(lèi)保存這些參數(shù)
3.使用建造者模式(Builder) - 對(duì)于傳入的參數(shù),有接口可以傳就使用接口
如需要傳入HashMap 則在方法中將參數(shù)類(lèi)型改為Map 避免使用者只能使用HashMap,也可以傳入其它Map接口的子類(lèi)型 - 對(duì)于布爾型參數(shù),使用枚舉更合適
例如晰绎,您可能有一個(gè)帶有靜態(tài)工廠的溫度計(jì)類(lèi)型( Thermometer 類(lèi))山宾,其值為枚舉:
public enum TemperatureScale { FAHRENHEIT, CELSIUS }
Thermometer.newInstance(TemperatureScale.CELSIUS)不僅比Thermometer.newInstance(true)更有意義,而且可以在將來(lái)的發(fā)行版中將Kelvin添加到TemperatureScale,而不需要 在Thermometer 類(lèi)中增加一個(gè)新的靜態(tài)工廠方法
Item41: 謹(jǐn)慎地使用重載
看如下的例子,我們想?yún)^(qū)分放進(jìn)去的是List或者set或者不知道什么類(lèi)型的集合,想一下它會(huì)如何打印
public class CollectionClassifier {
public static String classify(Set<?> s) {
return "Set";
}
public static String classify(List<?> lst) {
return "List";
}
public static String classify(Collection<?> c) {
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));
}
}
它會(huì)順序打印"Set","List","Unknown Collection"嗎?不,它會(huì)打印三次"Unknown Collection" 原因在于重載方法是在編譯時(shí)執(zhí)行,所以會(huì)以Collection<?>為準(zhǔn)
我們應(yīng)該避免使用相同參數(shù)數(shù)量的重載方法,使機(jī)器不懂,自己更易混淆
Item 42: 謹(jǐn)慎的使用可變參數(shù)
舉一個(gè)例子
static int sum(int... args) {
int sum = 0;
for (int arg : args)
sum += arg;
return sum;
}
sum(1,2,3) --> 6
sum() --> 0
暫略
Item 43: Return empty arrays or collections, not nulls
像如下的代碼很常見(jiàn)
//bad
private final List<Cheese> cheesesInStock = ...;
/**
* @return an array containing all of the cheeses in the shop,
* or null if no cheeses are available for purchase.
*/
public Cheese[] getCheeses() {
if (cheesesInStock.size() == 0)
return null;
...
}
調(diào)用者很可能粗心大意忘記判null導(dǎo)致異常,也許很多年之后才會(huì)發(fā)現(xiàn)
有人說(shuō)返回null避免了內(nèi)存開(kāi)銷(xiāo),首先你要證明是這段代碼導(dǎo)致的性能問(wèn)題,其次我們可以使用不可變的靜態(tài)常量聲明一個(gè)空集合
// The right way to return an array from a collection
private final List<Cheese> cheesesInStock = ...;
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
/**
* @return an array containing all of the cheeses in the shop.
*/
public Cheese[] getCheeses() {
if(cheesesInStock.size() <= 0)
return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}
//or
public List<Cheese> getCheeseList() {
if (cheesesInStock.isEmpty())
return Collections.emptyList(); // Always returns same list
else
return new ArrayList<Cheese>(cheesesInStock);
}
總之,使用集合或數(shù)組的任何情況下都不能返回null
Item 44: Write doc comments for all exposed API elements
為所有暴露出去的API寫(xiě)文檔注釋