對(duì)引用使用==(而不是.equals)
很多Java開(kāi)發(fā)者都知道使用==比較原生類型數(shù)據(jù),使用.equals比較引用類型數(shù)據(jù)。這是一條很容易記住的簡(jiǎn)單原則,Java開(kāi)發(fā)者這么用也沒(méi)什么問(wèn)題。有時(shí)使用==來(lái)比較標(biāo)準(zhǔn)的Java類型引用(String、Integer、Long等等)也沒(méi)問(wèn)題,不過(guò)這要取決于被緩存的值的大小,因此這么做并不是一個(gè)好的做法。有時(shí),我們需要檢查標(biāo)識(shí)的相等性而不是內(nèi)容的相等性,在這種情況下使用==來(lái)比較引用就很適合了。相對(duì)而言,我更喜歡Groovy的處理方式,==類似于.equals,而===則是更加嚴(yán)格地比較標(biāo)識(shí)。同理,使用!=來(lái)比較兩個(gè)引用也是一個(gè)“危險(xiǎn)信號(hào)”,因?yàn)槿绻容^的兩個(gè)對(duì)象不共享相同的標(biāo)識(shí)(內(nèi)存地址),即便他們擁有相同的內(nèi)容也總是會(huì)返回true。
對(duì)枚舉使用.equals(而不是==)
坦率地說(shuō),對(duì)于枚舉,Java開(kāi)發(fā)者使用==還是.equals都沒(méi)有太大關(guān)系。不過(guò),我更傾向于對(duì)枚舉使用==。這么做最重要的原因就是對(duì)枚舉使用==可以防止不小心將枚舉與不相關(guān)的對(duì)象進(jìn)行比較(永遠(yuǎn)不會(huì)相等)。Object.equals(Object)方法可以接收任意對(duì)象,這意味著編譯器并不會(huì)強(qiáng)制限定傳進(jìn)來(lái)的對(duì)象要與被比較的對(duì)象是相同的類型。一般來(lái)說(shuō),我更喜歡靜態(tài)的編譯期問(wèn)題檢測(cè)而非動(dòng)態(tài)運(yùn)行期的問(wèn)題檢測(cè),對(duì)枚舉使用==可以滿足這個(gè)要求。同理,在比較枚舉時(shí),!=與!.equals也是一樣的。
字符串常量
在看到有限的相關(guān)字符串常量時(shí),我就在想使用枚舉應(yīng)該更加適合。對(duì)于高度內(nèi)聚的字符串常量的情況來(lái)說(shuō)更是如此,因?yàn)槊杜e可以更好地表達(dá)出這些字符串所表示的概念。相比于字符串常量來(lái)說(shuō),枚舉提供了編譯期的靜態(tài)類型安全與潛在的性能優(yōu)勢(shì)。對(duì)于程序的正確性來(lái)說(shuō),編譯期的安全是最吸引我的地方。
equals(Object)與hashCode()方法的不匹配
雖然我認(rèn)為每個(gè)Java類都應(yīng)該重寫(xiě)toString()方法,但對(duì)于equals(Object)與hashCode()方法來(lái)說(shuō)卻并不這么認(rèn)為。我覺(jué)得只有在需要這些方法的場(chǎng)合下才應(yīng)該重寫(xiě)類中的這兩個(gè)方法,因?yàn)樗麄兊拇嬖诎凳局湓O(shè)計(jì)與開(kāi)發(fā)某種程度上的完全改變。特別地,equals與hashCode方法要能滿足其意圖與契約(位于Object類的API文檔),并且需要保持一致。大多數(shù)IDE與分析工具都會(huì)在其中一個(gè)方法重寫(xiě)而另一個(gè)沒(méi)有重寫(xiě)的情況下給出提示。然而,我要確保equals與hashCode使用的是相同的屬性,并且在這兩個(gè)方法中屬性的順序要保持一致。
源代碼中的注釋
雖然接口(通常是Javadoc)中缺乏注釋是一種危險(xiǎn)信號(hào),不過(guò)方法體和其他源代碼中的注釋也可能是個(gè)危險(xiǎn)信號(hào),會(huì)導(dǎo)致潛在的問(wèn)題。如果需要為代碼添加注釋以幫助人們理解代碼的行為,那就很有可能是代碼的復(fù)雜性過(guò)大(“注釋是惡臭代碼的芳香劑”,這句話有些滑稽,不過(guò)在一定程度上卻真的如此)。就像文中提到的眾多“危險(xiǎn)信號(hào)”一樣,有時(shí)我也認(rèn)為源代碼中的注釋是有意義的,可以提供很多信息,這時(shí)就沒(méi)有必要?jiǎng)h除這些注釋了。然而,我認(rèn)為代碼中的注釋(不是接口描述的Javadoc注釋)應(yīng)該盡量少一些,而且主要應(yīng)該關(guān)注于“為什么”這么寫(xiě)而不是“如何做”。代碼自身應(yīng)該能夠說(shuō)明“如何”這一問(wèn)題,不過(guò)很多時(shí)候是無(wú)法清楚地解釋出“為什么”(由于客戶/管理方向、設(shè)計(jì)決策、正式的接口需求、正式的算法需求等等)。
實(shí)現(xiàn)繼承(extends)
我看到很多時(shí)候使用extends(實(shí)現(xiàn)繼承)都很不錯(cuò),也適合于相應(yīng)的場(chǎng)景。但很多時(shí)候也存在使用不當(dāng)?shù)那闆r,實(shí)現(xiàn)繼承會(huì)導(dǎo)致更多的麻煩。Allen Holub曾說(shuō)到extends是邪惡的,四人幫的設(shè)計(jì)模式一書(shū)也用了很大的篇幅解釋為何說(shuō)組合要比實(shí)現(xiàn)繼承更好。隨著編寫(xiě)Java代碼時(shí)間的不斷增長(zhǎng),更為重要的是隨著我維護(hù)Java代碼時(shí)間的不斷增長(zhǎng),我越來(lái)越覺(jué)得組合相對(duì)于實(shí)現(xiàn)繼承的優(yōu)勢(shì)了。我確實(shí)看到了實(shí)現(xiàn)繼承所帶來(lái)的好處,不過(guò)更多的時(shí)候類是被“硬塞”到繼承體系當(dāng)中的,子類只不過(guò)是對(duì)UnsupportedOperationExceptions打洞或是“空實(shí)現(xiàn)”,因?yàn)樗麄兯^承下來(lái)的方法并不適用于他們自己。加上Effective Java一書(shū)中所提及的繼承問(wèn)題(比如說(shuō)具體類的equals與hashCode方法實(shí)現(xiàn)等),這真就成了一個(gè)大問(wèn)題了。實(shí)現(xiàn)繼承并不總是糟糕的,不過(guò)很多人經(jīng)常沒(méi)有正確地使用他們,因此我覺(jué)得這是個(gè)“危險(xiǎn)信號(hào)”。
無(wú)作用的代碼
程序中出現(xiàn)用不上的代碼永遠(yuǎn)都不是一件好事。人們很快就會(huì)忘記它是怎么用的,為什么要這么用。不久之后,開(kāi)發(fā)者就想知道這是不是出于某個(gè)原因被扔在那里的。無(wú)作用的代碼不僅會(huì)無(wú)意義地增加代碼長(zhǎng)度,增加維護(hù)的復(fù)雜度,對(duì)于IDE來(lái)說(shuō),無(wú)作用的代碼還有可能會(huì)被不小心調(diào)用,這種情況說(shuō)明代碼基出現(xiàn)了問(wèn)題。
注釋掉的代碼
注釋掉的代碼可能不像可執(zhí)行的、無(wú)意義的代碼那么糟糕,因?yàn)橹辽偎粫?huì)被不小心地調(diào)用,顯然,這些代碼是不會(huì)被用到的,不過(guò)這仍舊是個(gè)危險(xiǎn)信號(hào),因?yàn)樗砻骺赡軙?huì)出現(xiàn)潛在的代碼基問(wèn)題。就像無(wú)意義的可執(zhí)行代碼一樣,注釋掉的代碼越多,人們就越難理解代碼為何會(huì)出現(xiàn)在那里、為什么會(huì)被注釋掉、不使用了為什么不將其刪除掉。開(kāi)發(fā)者不敢刪除他們,因?yàn)榱粝聛?lái)肯定是有原因的,只不過(guò)沒(méi)人能夠記起來(lái)原因是什么了。
使用StringBuffer而非StringBuilder
坦白地說(shuō),這只是個(gè)小問(wèn)題而已,不過(guò)卻能標(biāo)識(shí)出過(guò)時(shí)的Java代碼(StringBuffer是JDK 1.0引入的,而StringBuilder則是J2SE 5引入的)或是開(kāi)發(fā)者并沒(méi)有真正理解他們之間的區(qū)別。在大多數(shù)情況下,這兩者之間的性能差別對(duì)于應(yīng)用來(lái)說(shuō)是微乎其微的,但由于StringBuilder更適合于大多數(shù)使用了StringBuffer的場(chǎng)景,因此我們還是可以從StringBuilder獲得性能上的微小提升。 我很少發(fā)現(xiàn)使用StringBuffer而不能使用StringBuilder的情況。方法與構(gòu)造方法中使用了過(guò)多的參數(shù)
當(dāng)方法與構(gòu)造方法擁有太多的參數(shù)時(shí),我總是擔(dān)心客戶端無(wú)法正確地使用他們,特別是有些參數(shù)是相同類型的情況。如果一個(gè)方法接收了3個(gè)字符串和3個(gè)布爾值,那么客戶端就很容易搞混傳遞進(jìn)去的值。編譯器在這種情況下也無(wú)能為力,檢測(cè)問(wèn)題的唯一辦法就是在運(yùn)行期(通過(guò)單元測(cè)試或是其他測(cè)試)查看調(diào)用結(jié)果。過(guò)多的參數(shù)表明了不恰當(dāng)?shù)脑O(shè)計(jì)。