每當編寫方法或者構造器時,應該考慮它的參數有哪些限制。應該把這些限制寫到文檔中,而且在這個方法體開頭處,經過顯示的檢查來實施這些限制。養成這樣的習慣很是重要。程序員
public final class Period { private final Date start; private final Date end; 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; } }
由於Date類自己時可變的,因此,數組
Date start = new Date(); Date end = new Date(); Period p = new Period(start, end); end.setYear(78); // 這個操做把實例的內部信息修改了。
爲了保護Period實例的內部信息避免受到這種攻擊,對於構造器的每一個可變參數進行保護性拷貝是必要的,而且使用備份對象做爲Period實例的組件,而不是使用原始的對象。安全
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); } }
注意,保護性拷貝是在檢查參數的有效性以前進行的,而且有效性檢查是針對拷貝以後的對象,而不是原始的對象。ide
可是改變Period實例仍然是有可能的,由於它的訪問方法提供了對其內部成員的訪問能力。性能
Date start = new Date(); Date end = new Date(); Period p = new Period(start, end); p.end().setYear(78); // 這個操做把實例的內部信息修改了。
爲了防護這第二種攻擊,只需改這兩個訪問方法,使它返回可變內部域的保護性拷貝便可。學習
public Date start() { return new Date(start.getTime()); } public Date end() { return new Date(end.getTime()); }
在內部組件被返回給客戶端以前,對它們進行保護性拷貝也是一樣的道理。測試
只要有可能,都應該使用不可變的對象做爲對象內部的組件,這樣就沒必要再爲保護性拷貝操心。ui
若是類具備從客戶端獲得或者返回到客戶端的可變組件,類就必須保護性地拷貝這些組件。
若是拷貝的成本受到限制,而且類信任它的客戶端會恰當地修改組件,就能夠在文檔中指明客戶端的職責使不得修改受到影響的組件,以此來代替保護性拷貝。this
首要目標是選擇易於理解的。spa
第二目標是選擇與大衆承認的名稱相一致的名稱。
每一個方法都應該盡其所能。方法太多會使類難以學習、使用、文檔化、測試和維護。
對於類和接口所支持的每一個動做,都提供一個功能齊全的方法。
目標是四個參數,或者更少。
有三種方法能夠縮短過長的參數列表。
第一種是把方法分解成多個方法,每一個方法只須要這些參數的一個子集。
第二種是建立輔助類,用來保存參數的分組。
第三種是從對象構建到方法調用都採用Builder模式。
若是使用的是類而不是接口,則限制了客戶端只能傳入特定的實現,若是碰巧輸入的數據是以其餘的形式存在,就會致使沒必要要的、可能很是昂貴的拷貝操做。
它是代碼更易於閱讀和編寫,也使之後更易於添加更多的選項。
下面的程序試圖根據一個集合是Set、List,仍是其餘的集合類型來分類。
public class CollectionClassifier { public static String classify(Set<?> set) { return "Set"; } public static String classify(List<?> list) { return "List"; } public static String classify(Collection<?> collection) { return "Unknow Collection"; } public static void main(String[] args) { Collection<?>[] collections = { new HashSet<String>(), new ArrayList<BigInteger>(), new HashMap<String, String>().values() }; for(Collection<?> collection : collections) { System.out.println(classify(collection)); } } }
可能指望會打印「Set」,接着是「List」,以及「Unknow Collection」,實際上不是這樣。而是「Unknow Collection」打印三次。
由於classify方法被重載(overload)了,而要調用哪一個重載方法是在編譯時作出決定的。對於for循環中的三次迭代,參數的編譯時類型都是相同的,Collection<?>。每次迭代的運行時類型都是不一樣的,但這並不影響對重載方法的選擇。由於該參數的編譯時類型爲Collection<?>,因此,惟一合適的重載方法是第三個,classify(Collection<?>),在循環的每次迭代中,都會調用這個重載方法。
對於重載方法(overload method)的選擇是靜態的,而對於被覆蓋的方法(overriden method)的選擇則是動態的。
選擇被覆蓋的方法的正確版本是在運行時的,選擇的依據時被調用方法所在對象的運行時類型。
再看看下面這個程序。
class Wine { String name() { return "wine"; } } class SparklingWine extends Wine { @Override String name() { return "sparkling wine"; } } class Champagne extends SparklingWine { @Override String name() { return "champagne"; } } public class Overriding { public static void main(String[] args) { Wine[] wines = { new Wine(), new SparklingWine(), new Champagne() }; for(Wine wine : wines) { System.out.println(wine.name()); } } }
這個程序打印「wine」、「sparkling wine」和「champagne」。
能夠對第一個程序修改一下,用單個方法替換三個重載的classify方法,以下。
public static String classify(Collection<?> collection) { return collection instanceof Set ? "Set" : collection instanceof List ? "List" : "Unknow Collection"; }
由於覆蓋機制時規範,而重載機制是例外。
到底怎樣纔算是胡亂使用重載機制呢?這個問題仍有爭議。安全而保守的策略是,永遠不要導出兩個具備相同參數數目的重載方法。
Java 1.5發行版本中增長了可變參數方法,通常稱做variable arity method(可匹配不一樣長度的變量方法)[JLS,8.4.1]。
可變參數方法接受零個或者多個指定類型的參數。可變參數機制經過先建立一個數組,數組的大小爲在調用位置所傳遞的參數數量,而後將參數值傳到數組中,最後將數組傳遞給方法。
static int sum(int... args) { int sum = 0; for (int arg : args) { sum += arg; } return sum; }
當真正須要讓一個方法帶有不定數量的參數時,可變參數就很是有效。可變參數是爲printf而設計的。printf和反射機制都從可變參數中極大地受益。
在重視性能地狀況下,須要當心。可變參數方法的每次調用都會致使進行一次數組分配和初始化。
須要可變參數的靈活性,但又沒法承受性能成本,能夠這麼作。
public void foo() {} public void foo(int a1) {} public void foo(int a1, int a2) {} public void foo(int a1, int a2, int a3) {} public void foo(int a1, int a2, int a3, int... rest) {}
在定義參數數目不定的方法時,可變參數方法是一種很方便的方式,可是不該該被過分濫用。若是使用不當,會產生混亂的結果。
對於一個返回null而不是零長度數組或者集合的方法,幾乎每次調用該方法都須要曲折的處理方式。這樣很容易出錯,由於編寫客戶端程序的程序員可能會忘記寫專門的代碼來處理null返回值。