Android基礎進階之EffectJava翻譯系列(第六章:方法)
這一章介紹方法設計的幾個方面:如何對待引數和返回值,如何設計方法簽名,如何註釋方法
Item38: 檢查引數的合法性
大部分使用的方法引數都有一定的限制,如不為null,size>0等
通用的原則就是預防大於整改,提前發現錯誤可以更快的規避問題,而不是在程式執行中發生
對於公共方法,使用Javadoc@塊標記,來記錄在違反引數值限制時丟擲的異常(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 }
對於私有的方法則使用斷言
// 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在實際專案中使用的很少,更多的還是使用的if判斷
每次寫一個方法或者建構函式的時候,在方法的開頭考慮引數的合法性是必不可少的
Item39:必要的時候使用拷貝物件
Java是一門安全的語言,即使在Java語言中,也要假設使用者正想方設法的破壞你的程式.除了少部分人想破壞系統的安全性,大部分問題都是程式設計人員可以控制的
雖然沒有物件的幫助,另一個類不太可能改變物件的內部狀態,但偶爾也有疏忽的地方
//一個不可變的週期類 // 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物件是可變的
// bad Date start = new Date(); Date end = new Date(); Period p = new Period(start, end); end.setYear(78); // 修改了p的內部狀態
為了保護物件的狀態,我們需要在建構函式中對可變引數執行拷貝防禦
// 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()); }
這裡沒有使用clone方法,因為Date有其它不可信任的子類
經驗上講,在內部儘量不要使用可變的類,如Date,可用long型替代Date.getTime()
總結:如果一個類呼叫了或返回可變的物件,則需要用拷貝物件防禦,如果很信任呼叫者不會修改類的內部狀態,則需要有一份警告文件提示呼叫者不能修改類的狀態
Item 40: 如何設計方法
-
選擇一個合適的方法名稱
你的主要目標是設計一個利於理解的方法名,次要目標是方法名稱之間保持協調性,如向資料庫中插入一條資料,有的使用addXX,有的使用setXX,有的使用insertXX,儘量保持統一 -
不要過分的使用方法
太多的方法容易使一個類難於維護和測試,只要當它需要經常呼叫的時候才考慮提出一個方法,否則就不管它 -
避免參數長的方法
儘量保持在四個引數或以下
有三種方式避免長引數
1.提出更多的方法
2.使用輔助類儲存這些引數
3.使用建造者模式(Builder) -
對於傳入的引數,有介面可以傳就使用介面
如需要傳入HashMap 則在方法中將引數型別改為Map 避免使用者只能使用HashMap,也可以傳入其它Map介面的子型別 -
對於布林型引數,使用列舉更合適
例如,您可能有一個帶有靜態工廠的溫度計型別( Thermometer 類),其值為列舉:
public enum TemperatureScale { FAHRENHEIT, CELSIUS }
Thermometer.newInstance(TemperatureScale.CELSIUS)不僅比Thermometer.newInstance(true)更有意義,而且可以在將來的發行版中將Kelvin新增到TemperatureScale,而不需要 在Thermometer 類中增加一個新的靜態工廠方法
Item41: 謹慎地使用過載
看如下的例子,我們想區分放進去的是List或者set或者不知道什麼型別的集合,想一下它會如何列印
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)); } }
它會順序列印"Set","List","Unknown Collection"嗎?不,它會列印三次"Unknown Collection" 原因在於過載方法是在編譯時執行,所以會以Collection<?>為準
我們應該避免使用相同引數數量的過載方法,使機器不懂,自己更易混淆
Item 42: 謹慎的使用可變引數
舉一個例子
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
像如下的程式碼很常見
//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; ... }
呼叫者很可能粗心大意忘記判null導致異常,也許很多年之後才會發現
有人說返回null避免了記憶體開銷,首先你要證明是這段程式碼導致的效能問題,其次我們可以使用不可變的靜態常量宣告一個空集合
// 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); }
總之,使用集合或陣列的任何情況下都不能返回null