1. 程式人生 > >《Effective Java》第7章 方法

《Effective Java》第7章 方法

spark 版本 integer 繼承 有效 內部 for -1 vararg

第38條:檢查參數的有效性

對於公有的方法,要用javadoc的@throws標簽(tag)在文檔中說明違反參數值限制時會拋出的異常。這樣的異常通常為IllegalArgumentException, IndexOutOfBoundsException或NullPointerException.
技術分享

非公有的方法通常應該使用斷言(assertion)來檢查它們的參數,具體做法如下所示:
技術分享

第39條:必要時進行保護性拷貝

沒有對象的幫助時,雖然另個類不可能修改對象的內部狀態,但是對象很容易在無意識的情況下提供這種幫助。例如,考慮下面的類,它聲稱可以表示一段不可變的時間周期:
技術分享
乍一看,這個類似乎是不可變的,並且強加了約束條件:周期的起始時間(start)不能在結束時間(end)之後。然而,因為Date類本身是可變的,因此很容易違反這個約束條件:
技術分享

為了保護Period實例的內部信息避免受到這種攻擊,時於構造器的每個可變參數進行保護性烤貝( defensive copy)是必要的,並且使用備份對象作為Period實例的組件,而不使用原始的對象:
技術分享

註意,保護性拷貝是在檢查參數的有效性之前進行的,並且有效性檢查是針時拷貝之後的對象,而不是針時原始的時象。雖然這樣做看起來有點不太自然,卻是必要的。

第41條:慎用重載

一個反例:
技術分享

你可能期望這個程序會打印出"Set" ,緊接著是“List",以及“Unknown Collection" ,但實際上不是這樣。它是打印“Unknown Collection"三次。

classify方法被重載(overloaded)了,而要調用哪個重載(overloading)方法是在編譯時做出決定的。對於for循環中的全部三次叠代,參數的編譯時類型都是相同的:Collections<>。每次叠代的運行時類型都是不同的,但這並不影響對重載方法的選擇。因為該參數的編譯時類型為Collection<>.

這個程序的行為有悖常理,因為對於重載方法(overloaded method ]的選擇是靜態的,而對於被覆蓋的方法(overridden method)的選擇則是動態的。選擇被覆蓋的方法的正確版本是在運行時進行的,選擇的依據是被調用方法所在對象的運行時類型。

技術分享

name方法是在類Wine中被聲明的,但是在類SparklingWine和Champagne中被覆蓋。正如你所預期的那樣,這個程序打印出“wine, sparkling wine和champagne" ,盡管在循環的每次叠代中,實例的編譯時類型都為Wine。當調用被覆蓋的方法時,對象的編譯時類型不會影響到哪個方法將被執行; “最為具體的(most specific)”那個覆蓋版本總是會得到執行。

對classify方法的最佳修正方案是,用單個方法來替換這三個重載的classify方法,並在這個方法中做一個顯式的instanceof測試:
技術分享

因為覆蓋機制是規範,而重載機制是例外,所以,覆蓋機制滿足了人們對於方法調用行為的期望。正如CollectionClassifier例子所示,重載機制很容易使這些期望落空。

到底怎樣才算胡亂使用重載機制呢?這個問題仍有爭議. 安全而保守的策略是,永遠不要導出兩個具有相同參數數目的重載方法。如果方法使用可變參數(varargs),保守的策略是根本不要重載它.

你始終可以給方法起不同的名稱,而不使用重載機制。

對於構造器,你沒有選擇使用不同名稱的機會;一個類的多個構造器總是重載的。在許多情況下,可以選擇導出靜態工廠,而不是構造器.

JDK一個反例
在Java 1.5發行版本之前,所有的基本類型都根本不同於所有的引用類型。但是當自動裝箱出現之後,就不再如此了,它會導致真正的麻煩。考慮下面這個程序:
技術分享
如果像大多數人一樣。希望程序從集合和列表中去除非整數值(0, 1和2),並打印出[-3, -2, -1] [-3, -2, -1]。事實上,打印出[-3, -2, -1] [-2, 0, -2] .

實際發生的情況是: set.remove(i)調用選擇重載方法remove(E),這裏的E是集合(Integer)的元素類型,將i從int自動裝箱到Integer中。這是你所期待的行為,因此程序不會從集合中去除正值。另一方面,list.remove(i)調用選擇重載方法remove(int i),它從列表的指定位置上去除元素。

第42條:慎用可變參數

可變參數方法接受0個或者多個指定類型的參數。可變參數機制通過先創建一個數組,數組的大小為在調用位置所傳遞的參數數量,然後將參數值傳到數組中,最後將數組傳遞給方法。

printf和反射機制都從可變參數中極大地受益。

第43條:返回零長度的數組或者集合,而不是null

對於一個返回null而不是零長度數組或者集合的方法,幾乎每次用到該方法時都需要這種曲折的處理方式。這樣做很容易出錯,因為編寫客戶端程序的程序員可能會忘記寫這種專門的代碼來處理null返回值。

有時候會有人認為:null返回值比零長度數組更好,因為它避免了分配數組所需要的開銷。這種觀點是站不住腳的,原因有兩點口第一,在這個級別上擔心性能問題是不明智的,除非分析表明這個方法正是造成性能問題的真正源頭(見第55條)。第二,對於不返回任何元素的調用,每次都返回同一個零長度數組是有可能的,因為零長度數組是不可變的,而不可變對象有可能被自由地共享(見第15條).

比如可以這樣做:
技術分享
技術分享

同樣地,集合值的方法也可以做成在每當需要返回空集合時都返回同一個不可變的空集合。Collections.emptySet, emptyList和emptyMap方法提供的正是你所需要的,如下所示:
技術分享

簡而言之,返回類型為數組或集合的方法沒理由返回null,而不是返回一個零長度的數組或者集合。

第44條:為所有導出的API元素編寫文檔註釋

為了正確地編寫API文檔,必須在每個被導出的類、接口、構造器、方法和域聲明之前增加一個文檔註釋。

多行的代碼示例前使用字符

{ @code ,然後在代碼後面加上}

不要忘記,為了產生包含HTML元字符的文檔,比如小於號(<)、大於號(>)以及“與”號(&),必須采取特殊的動作。讓這些字符出現在文檔中的最佳辦法是用{@literal}標簽將它們包圍起來,這樣就限制HTML標記和嵌套的Javadoc標簽的處理。

Java 1.5發行版本中增加的三個特性在文檔註釋中需要特別小心:泛型、枚舉和註解. 當為泛型或者方法編寫文檔時,確保要在文檔中說明所有的類型參數。
技術分享

當為枚舉類型編寫文檔時,要確保在文檔中說明常童,以及類型,還有任何公有的方法。註意,如果文檔註釋很簡短,可以將整個註釋放在一行上:
技術分享

為註解類型編寫文檔時,要確保在文檔中說明所有成員,以及類型本身。
技術分享
技術分享
從Java 1.5發行版本開始,包級私有的文檔註釋就應該放在一個稱作package-info.java的文件中,而不是放在package.html中。除了包級私有的文檔註釋之外,package-info.java也可以(但並非必需)包含包聲明和包註解。

Javadoc具有“繼承”方法註釋的能力。如果API元素沒有文檔註釋,Javadoc將會搜索最為適用的文檔註釋,接口的文檔註釋優先於超類的文檔註釋。

也可以利用{@inheritDoc}標簽從超類型中繼承文檔註釋的部分內容。這意味著,不說別的,類還可以重用它所實現的接口的文檔註釋,而不需要拷貝這些註釋。

《Effective Java》第7章 方法