終于翻開這本James都稱贊的java經(jīng)典書籍了,發(fā)現(xiàn)比一般的英語書籍要難懂一些。但是里面的Item都是非常實(shí)用的,是java程序員應(yīng)該理解的。
Methods Common to All Objects
?
item 7:當(dāng)你覆蓋equals方法的時(shí)候一定要遵守general contact
?
???覆蓋equals的時(shí)候一定要加倍的小心,其實(shí)最好的辦法就是不覆蓋這個(gè)方法。比如在下面的情況下就可以不覆蓋
???1這個(gè)類的每個(gè)實(shí)例都是唯一的,例如Thread類
???2 如果你不關(guān)心這個(gè)類是否該提供一個(gè)測(cè)試邏輯相等的方法
???3超類已經(jīng)覆蓋了equals方法,并且它合適子類使用
???4如果這個(gè)類是private或者是package-private的,并且你確信他不會(huì)被調(diào)用
?
???但是當(dāng)我們要為這個(gè)類提供區(qū)分邏輯相等和引用相等的方法的時(shí)候,我們就必須要覆蓋這個(gè)方法了。例如String類,Date類等,覆蓋的時(shí)候我們一定要遵從general contact,說白了就是一個(gè)合同。合同的主要內(nèi)容是
???1.x.equals(x)必須返回true
???2.x.equals(y)當(dāng)且僅當(dāng)y.equals(x)返回true的時(shí)候返回true
???3.x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)必須返回true
???4.如果沒有任何修改得話那么多次調(diào)用x.equals(y)的返回值應(yīng)該不變
???5.任何時(shí)候非空的對(duì)象x,x.equals(null)必須返回false
下面是作者的建議如何正確的覆蓋equals方法
1.? 用==檢查是否參數(shù)就是這個(gè)對(duì)象的引用
2.? 用instanceof判斷參數(shù)的類型是否正確
3.? 把參數(shù)轉(zhuǎn)換成合適的類型
4.? 比較類的字段是不是匹配
例如:
public boolean equals(Object o)
{
?????? if(o== this) return true;
?????? if(!(o instanceof xxxx) return false;
?????? xxx in = (xxx)o;
?????? return ……..
}
最后一點(diǎn)要注意的時(shí)候不要提供這樣的方法public boolean equals(MyClass o)這樣是重載并不是覆蓋Object的equals方法
item 8 :當(dāng)你覆蓋equals的時(shí)候必須覆蓋hashCode方法
??? 這點(diǎn)必須切忌,不然在你和hash-based集合打交道的時(shí)候,錯(cuò)誤就會(huì)出現(xiàn)了。關(guān)鍵問題在于一定要滿足相等的對(duì)象必須要有相等的hashCode。如果你在PhoneNumber類中覆蓋了equals方法,但是沒有覆蓋hashCode方法,那么當(dāng)你做如下操作的時(shí)候就會(huì)出現(xiàn)問題了。
Map m = new HashMap();
m.put(new PhoneNumber(408,863,3334),”ming”)
當(dāng)你調(diào)用m.get(new PhoneNumber(408,863,3334))的時(shí)候你希望得到ming但是你卻得到了null,為什么呢因?yàn)樵谡麄€(gè)過程中有兩個(gè)PhoneNumber的實(shí)例,一個(gè)是put一個(gè)是get,但是他們兩個(gè)邏輯相等的實(shí)例卻得到不同的hashCode那么怎么可以取得以前存入的ming呢。
?
Item 9:永遠(yuǎn)覆蓋toString方法
??? 在Object的toString方法返回的形式是Class的類型加上@加上16進(jìn)制的hashcode。你最好在自己的類中提供toString方法更好的表述實(shí)例的信息,不然別人怎么看得明白呢。
?
Item 10:覆蓋clone()方法的時(shí)候一定要小心
??? 一個(gè)對(duì)象要想被Clone,那么要實(shí)現(xiàn)Clone()接口,這個(gè)接口沒有定義任何的方法,但是如果你不實(shí)現(xiàn)這個(gè)接口的話,調(diào)用clone方法的時(shí)候會(huì)出現(xiàn)CloneNotSupportedException,這就是作者叫做mixin的接口類型。通常clone()方法可以這樣覆蓋
public Object clone()
{
try
{
????????????? return super.clone();
}
catch(CloneNotSupportedException e)
{}
}
但是當(dāng)你要clone的類里面含有可修改的引用字段的時(shí)候,那么你一定要把整個(gè)類的藍(lán)圖進(jìn)行復(fù)制,如果對(duì)你clone得到的對(duì)象進(jìn)行修改的時(shí)候還會(huì)影響到原來的實(shí)例,那么這是不可取的。所以應(yīng)該這樣clone()
public Object clone() throws CloneNotSupportedException
{
?????? Stack Result? = (Stack)super.clone();
?????? Result.elements = (Object[])elements.clone();
?????? Return result;
}
其中elements是stack類中可修改的引用字段,注意如果elements是final的話我們就無能為力了,因?yàn)椴荒芙o他重新賦值了.其實(shí)如果不是必須的話,根本就不用它最好。
?
Item 11:考慮適當(dāng)?shù)臅r(shí)候覆蓋Comparable接口
?????Thinking in java上說的更清楚,這里不多少了。
??? 越來越發(fā)現(xiàn)這是一本難得的好書,Java程序員不看這本書的話真是很遺憾。本章講述的是類和接口相關(guān)的問題。這幾個(gè)Item都非常重要. Item 12:把類和成員的可訪問范圍降到最低
????好的模塊設(shè)計(jì)應(yīng)該盡最大可能封裝好自己的內(nèi)部信息,這樣可以把模塊之間的耦合程度降到最低。開發(fā)得以并行,無疑這將加快開發(fā)的速度,便于系統(tǒng)地維護(hù)。Java中通過訪問控制符來解決這個(gè)問題。
- public表示這個(gè)類在任何范圍都可用。
- protected表示只有子類和包內(nèi)的類可以使用
- private-package(default)表示在包內(nèi)可用
- private表示只有類內(nèi)才可以用
你
在設(shè)計(jì)一個(gè)類的時(shí)候應(yīng)該盡量的按照4321得順序設(shè)計(jì)。如果一個(gè)類只是被另一個(gè)類使用,那么應(yīng)該考慮把它設(shè)計(jì)成這個(gè)類的內(nèi)部類。通常public的類不應(yīng)
該有public得字段,不過我們通常會(huì)用一個(gè)類來定義所有的常量,這是允許的。不過必須保證這些字段要么是基本數(shù)據(jù)類型要么引用指向的對(duì)象是不可修改
的。不然他們將可能被修改。例如下面的定義中data就是不合理的,后面兩個(gè)沒有問題。
public class Con
{
??????public static final int[] data = {1,2,3};// it is bad
??????public static final String hello = "world";
??????public static final int i = 1;
}
Item 13:不可修改的類更受青睞
????不可修改的類意思是他們一經(jīng)創(chuàng)建就不會(huì)改變,例如String類。他們的設(shè)計(jì)、實(shí)現(xiàn)都很方便,安全性高——它們是線程安全的。設(shè)計(jì)不可修改類有幾點(diǎn)規(guī)則:
- 不要提供任何可以修改對(duì)象的方法
- 確保沒有方法能夠被覆蓋,可以通過把它聲明為final
- 所有字段設(shè)計(jì)成final
- 所有字段設(shè)計(jì)成private
- 確保外部不能訪問到類的可修改的組件
不可修改類也有個(gè)缺點(diǎn)就是創(chuàng)建不同值得類的時(shí)候要?jiǎng)?chuàng)建不同的對(duì)象,String就是這樣的。通常有個(gè)解決的辦法就是提供一個(gè)幫助類來彌補(bǔ),例如StringBuffer類。
Item 14:化合(合成)比繼承更值得考慮
??????
實(shí)現(xiàn)代碼重用最重要的辦法就是繼承,但是繼承破壞了封裝,導(dǎo)致軟件的鍵壯性不足。如果子類繼承了父類,那么它從父類繼承的方法就依賴父類的實(shí)現(xiàn),一旦他改
變了會(huì)導(dǎo)致不可預(yù)測(cè)的結(jié)果。作者介紹了InstrumentedHashSet作為反例進(jìn)行說明,原因就是沒有明白父類的方法實(shí)現(xiàn)。作者給出的解決辦法是
通過化合來代替繼承,用包裝類和轉(zhuǎn)發(fā)方法來解決問題。把想擴(kuò)展的類作為本類的一個(gè)private
final得成員變量。把方法參數(shù)傳遞給這個(gè)成員變量并得到返回值。這樣做的缺點(diǎn)是這樣的類不適合回掉框架。繼承雖然好,我們卻不應(yīng)該濫用,只有我們能確
定它們之間是is-a得關(guān)系的時(shí)候才使用。
Item 15:如果要用繼承那么設(shè)計(jì)以及文檔都要有質(zhì)量保證,否則就不要用它
????為了避免繼承帶來的問題,你必須提供精確的文檔來說明覆蓋相關(guān)方法可能出現(xiàn)的問題。在構(gòu)造器內(nèi)千萬不要調(diào)用可以被覆蓋的方法,因?yàn)樽宇惛采w方法的時(shí)候會(huì)出現(xiàn)問題。
import java.util.*;
public class SubClass extends SuperClass
{
?private final Date date;
?
?public SubClass()
?{
??date = new Date();?
?}
?
?public void m()
?{
??System.out.println(date);?
?}
?
?public static void main(String[] args)
?{
??SubClass s = new SubClass();
??s.m();?
?}
?
}
class SuperClass
{
?public SuperClass()
?{
??m();?
?}?
?
?public void m()
?{
??
?}
}
由于在date被初始化之前super()已經(jīng)被調(diào)用了,所以第一次輸出null而不是當(dāng)前的時(shí)間。
由于在Clone()或者序列化的時(shí)候非常類似構(gòu)造器的功能,因此readObject()和clone()方法內(nèi)最好也不要包括能被覆蓋的方法。
Item 16:在接口和抽象類之間優(yōu)先選擇前者
??????接口和抽象類都用來實(shí)現(xiàn)多態(tài),不過我們應(yīng)該優(yōu)先考慮用接口。知道嗎?James說過如果要讓他重新設(shè)計(jì)java的話他會(huì)把所有都設(shè)計(jì)成接口的。抽象類的優(yōu)點(diǎn)是方便擴(kuò)展,因?yàn)樗潜焕^承的,并且方法可以在抽象類內(nèi)實(shí)現(xiàn),接口則不行。
Item 17:接口只應(yīng)該用來定義類型
??????接口可以這樣用的 Collection c = new xxxx();這是我們最常用的。不要把接口用來做其他的事情,比如常量的定義。你應(yīng)該定義一個(gè)類,里面包含public final static 得字段。
Item 18: 在靜態(tài)和非靜態(tài)內(nèi)部類之間選擇前者
??????如果一個(gè)類被定義在其他的類內(nèi)部那么它就是嵌套類,可以分為靜態(tài)內(nèi)部類、非靜態(tài)內(nèi)部類和匿名類。
???static
member class 得目的是為enclosing class服務(wù),如果還有其他的目的,就應(yīng)該把它設(shè)計(jì)成top-level
class。nonstatic member class是和enclosing class
instance關(guān)聯(lián)的,如果不需要訪問enclosing class
instance的話應(yīng)該把它設(shè)計(jì)成static得,不然會(huì)浪費(fèi)時(shí)間和空間。anonymous
class是聲明和初始化同時(shí)進(jìn)行的。可以放在代碼的任意位置。典型應(yīng)用是Listener 和process object例如Thread。
??? 由于以前學(xué)過C語言,所以對(duì)C還是蠻有感情,而JAVA和C又有很多相似之處,很多從C轉(zhuǎn)過來學(xué)習(xí)JAVA的兄弟,可能一開始都不是很適應(yīng),因?yàn)楹芏嘣?/span>C里面的結(jié)構(gòu)在JAVA里面都不能使用了,所以下面我們來介紹一下C語言結(jié)構(gòu)的替代。
?????
????? Item 19:用類代替結(jié)構(gòu)
????? JAVA剛面世的時(shí)候,很多C程序員都認(rèn)為用類來代替結(jié)構(gòu)現(xiàn)在太復(fù)雜,代價(jià)太大了,但是實(shí)際上,如果一個(gè)JAVA的類退化到只包含一個(gè)數(shù)據(jù)域的話,這樣的類與C語言的結(jié)構(gòu)大致是等價(jià)的。
????? 比方說下面兩個(gè)程序片段:
????? class Point
????? {
?????? private float x;
?????? private float y;
????? }
????? 實(shí)際上這段代碼和C語言的結(jié)構(gòu)基本上沒什么區(qū)別,但是這段代碼恐怕是眾多OO設(shè)計(jì)Fans所不齒的,因?yàn)樗鼪]有體現(xiàn)封裝的優(yōu)異性,沒有體現(xiàn)面向?qū)ο笤O(shè)計(jì)的優(yōu)點(diǎn),當(dāng)一個(gè)域被修改的時(shí)候,你不可能再采取任何輔助的措施了,那我們?cè)賮砜匆豢床捎冒接杏蚝凸灿性L問方法的OO設(shè)計(jì)代碼段:
????? class Point
????? {
?????? private float x;
?????? private float y;
?????? public Point(float x,float y)
?????? {
???????????? this.x=x;
???????????? this.y=y;
?????? }
??????? public float getX(){retrun x;}
??????? public float getY(){return y;}
??????? public void setX(float x){this.x=x;}
??????? public void setY(float y){this.y=y;}
????? }
?
??????? 單從表面上看,這段代碼比上面那個(gè)多了很多行,還多了很多函數(shù),但是仔細(xì)想一下,這樣的OO設(shè)計(jì),似乎更人性化,我們可以方面的對(duì)值域進(jìn)行提取,修改等操作,而不直接和值域發(fā)生關(guān)系,這樣的代碼不僅讓人容易讀懂,而且很安全,還吸取了面向?qū)ο蟪绦蛟O(shè)計(jì)的靈活性,試想一下,如果一個(gè)共有類暴露它的值域,那么想要在將來的版本中進(jìn)行修改是impossible的,因?yàn)楣灿蓄惖目蛻舸a已經(jīng)遍布各處了。
需要提醒一點(diǎn)的是,如果一個(gè)類是包級(jí)私有的,或者是一個(gè)私有的嵌套類,則直接暴露其值域并無不妥之處。
?
Item 20:用類層次來代替聯(lián)合
我們?cè)谟?/span>C語言來進(jìn)行開發(fā)的時(shí)候,經(jīng)常會(huì)用到聯(lián)合這個(gè)概念,比如:
?????? typedef struct{
???? double length;
???? double width;????
}rectangleDimensions_t;
那我們?cè)?/span>JAVA里面沒有聯(lián)合這個(gè)概念,那我們用什么呢?對(duì)!用繼承,這也是JAVA最吸引我的地方之一,它可以使用更好的機(jī)制來定義耽擱數(shù)據(jù)類型,在Bruce Eckel的Thinking in java里面也多次提到了一個(gè)和形狀有關(guān)的例子,我們可以先籠統(tǒng)的定義一個(gè)抽象類,即我們通常所指的超類,每個(gè)操作定義一個(gè)抽象的方法,其行為取決于標(biāo)簽的值,如果還有其他的操作不依賴于標(biāo)簽的值,則把操作變成根類(繼承的類)中的具體方法。
這樣做的最重要的優(yōu)點(diǎn)是:類層次提供了類型的安全性。
其次代碼非常明了,這也是OO設(shè)計(jì)的優(yōu)點(diǎn)。
而且它很容易擴(kuò)展,即使是面向多個(gè)方面的工作,能夠同樣勝任。
最后它可以反映這些類型之間本質(zhì)上的層次關(guān)系,從而允許更強(qiáng)的靈活性,以便編譯時(shí)類型檢查。
?
Item 21:用類來代替enum結(jié)構(gòu)
Java程序設(shè)計(jì)語言提出了類型安全枚舉的模式來替代enum結(jié)構(gòu),它的基本思想很簡單:定義一個(gè)類來代表枚舉類型的單個(gè)元素,并且不提供任何公有的構(gòu)造函數(shù),相反,提供公有靜態(tài)final類,使枚舉類型中的每一個(gè)常量都對(duì)應(yīng)一個(gè)域。
類型安全枚舉類型的一個(gè)缺點(diǎn)是,裝載枚舉類的和構(gòu)造常量對(duì)象時(shí),需要一定的時(shí)間和空間開銷,除非是在資源很受限制的設(shè)備比如蜂窩電哈和烤面包機(jī)上,否則在實(shí)際中這個(gè)問題不會(huì)被考慮。
?總之,類型安全枚舉類型明顯優(yōu)于int類型,除非實(shí)在一個(gè)枚舉類型主要被用做一個(gè)集合元素,或者主要用在一個(gè)資源非常不受限的環(huán)境下,否則類型安全枚舉類型的缺點(diǎn)都不成問題,依次,在要求使用一個(gè)枚舉類型的環(huán)境下,我們首先應(yīng)考慮類型安全枚舉類型模式。
?
Item 22:用類和接口來代替函數(shù)指針
眾所周知,JAVA語言和C的最大區(qū)別在于,前者去掉了指針,小生第一次接觸JAVA的時(shí)候覺得好不習(xí)慣,因?yàn)橥蝗灰幌伦記]了指針,覺得好不方面啊,C語言的精髓在于其指針的運(yùn)用,而JAVA卻把它砍掉了,讓人好生郁悶,不過隨著時(shí)間的推移,我漸漸明白了用類和接口的應(yīng)用也同樣可以提供同樣的功能,我們可以直接定義一個(gè)這樣一個(gè)類,他的方法是執(zhí)行其他方法上的操作,如果一個(gè)類僅僅是導(dǎo)出這樣一個(gè)方法,那么它實(shí)際上就是一個(gè)指向該方法的指針,舉個(gè)例子:
?class StringLengthComprator{
public int compare(String s1,String s2)
{
return s1.length()-s2.length();
}
}
這
個(gè)類導(dǎo)出一個(gè)帶兩個(gè)字符串的方法,它是一個(gè)用于字符串比較的具體策略。它是無狀態(tài)的,沒有域,所以,這個(gè)類的所有實(shí)例在功能上都是等價(jià)的,可以節(jié)省不必要
的對(duì)象創(chuàng)建開銷。但是我們不好直接把這個(gè)類傳遞給可戶使用,因?yàn)榭蓱魺o法傳遞任何其他的比較策略。相反,我們可以定義一個(gè)接口,即我們?cè)谠O(shè)計(jì)具體策略類的
時(shí)候還需要定義一個(gè)策略接口:
????? public interface Comparator{
?????????? public int compare(Object o1,Object o2);
}
? 我們完全可以依照自己的需要來定義它。
具體的策略類往往使用匿名類聲明。
在JAVA中,我們?yōu)榱藢?shí)現(xiàn)指針的模式,聲明一個(gè)接口來表示該策略,并且為每個(gè)具體策略聲明一個(gè)實(shí)現(xiàn)了該接口的類,如果一個(gè)具體策略只被使用一次的話,那么通常使用匿名類來聲明和實(shí)例化這個(gè)具體策略類,如果一個(gè)策略類反復(fù)使用,那么它的類通常是一個(gè)私有的的靜態(tài)成員類。
下面我們來討論一下有關(guān)方法設(shè)計(jì)的幾個(gè)方面,下面說的幾個(gè)要點(diǎn)大多數(shù)都是應(yīng)用在構(gòu)造函數(shù)中,當(dāng)然也使用于普通方法,我們追求的依然是程序的可用性,健壯性和靈活性。
?
Item 23:檢查參數(shù)的有效性
非公有的方法我們應(yīng)該用斷言的方法來檢查它的參數(shù),而不是使用通常大家所熟悉的檢查語句來檢測(cè)。如果我們使用的開發(fā)平臺(tái)是JDK1.4或者更高級(jí)的平臺(tái),我們可以使用assert結(jié)構(gòu);否則我們應(yīng)該使用一種臨時(shí)的斷言機(jī)制。
有些參數(shù)在使用過程中是先保存起來,然后在使用的時(shí)候再進(jìn)行調(diào)用,構(gòu)造函數(shù)正是這種類型的一種體現(xiàn),所以我們通常對(duì)構(gòu)造函數(shù)參數(shù)的有效性檢查是非常仔細(xì)的。
?
Item 24:需要時(shí)使用保護(hù)性拷貝
眾所周知,JAVA在代碼安全性方面較C/C++有顯著的提高,緩沖區(qū)溢出,數(shù)組越界,非法指針等等,我們的JAVA都有一個(gè)很完善的機(jī)制來進(jìn)行免疫,但是這并不代表我們不必去考慮JAVA的安全性,即便在安全的語言,如果不采取措施,還是無法使自己與其他類隔開。假設(shè)類的客戶會(huì)盡一切手段來破壞這個(gè)類的約束條件,在這樣的前提下,你必須從保護(hù)性的方面來考慮設(shè)計(jì)程序。通過大量的程序代碼研究我們得出這樣的結(jié)論:對(duì)于構(gòu)造性函數(shù)的每個(gè)可變參數(shù)進(jìn)行保護(hù)性拷貝是必要的。需要注意的是,保護(hù)性拷貝是在檢查參數(shù)的有效性之前進(jìn)行的,并且有效性檢查是針對(duì)拷貝之后的對(duì)象,而不是原始的對(duì)象。對(duì)于“參數(shù)類型可以被不可信方子類化”的情況,不要用clone方法來進(jìn)行參數(shù)的保護(hù)性拷貝。
對(duì)
于參數(shù)的保護(hù)性拷貝并不僅僅在于非可變類,當(dāng)我們編寫一個(gè)函數(shù)或者一個(gè)構(gòu)造函數(shù)的時(shí)候,如果它要接受客戶提供的對(duì)象,允許該對(duì)象進(jìn)入到內(nèi)部數(shù)據(jù)結(jié)構(gòu)中,則
有必要考慮一下,客戶提供的對(duì)象是否是可變的,如果是,則要考慮其變化的范圍是否在你的程序所能容納的范圍內(nèi),如果不是,則要對(duì)對(duì)象進(jìn)行保護(hù)性拷貝,并且
讓拷貝之后的對(duì)象而不是原始對(duì)象進(jìn)入到數(shù)據(jù)結(jié)構(gòu)中去。當(dāng)然最好的解決方法是使用非可變的對(duì)象作為你的對(duì)象內(nèi)部足見,這樣你就可以不必關(guān)心保護(hù)性拷貝問題
了。):
?
Item 25:謹(jǐn)慎使用設(shè)計(jì)方法的原型
(1)謹(jǐn)慎的選擇方法的名字:即要注意首先要是易于理解的,其次還要與該包中的其他方法的命名風(fēng)格相一致,最后當(dāng)然要注意取一個(gè)大眾所認(rèn)可的名字。
(2)
不要追求提供便利的方法:每一個(gè)方法都應(yīng)該提供其應(yīng)具備的功能點(diǎn),對(duì)于接口和類來方法不要過多,否則會(huì)對(duì)學(xué)習(xí)使用維護(hù)等等方面帶來許多不必要的麻煩,對(duì)于
每一個(gè)類型所支持的每一個(gè)動(dòng)作,都提供一個(gè)功能完全的方法,只有一個(gè)方法過于頻繁的使用時(shí),才考慮為它提供一個(gè)快捷方法。
(3)
避免過長的參數(shù)列表:通常在實(shí)踐中,我們以三個(gè)參數(shù)作為最大值,參數(shù)越少越好,類型相同的長參數(shù)列尤其影響客戶的使用,兩個(gè)方法可以避免過長的參數(shù)這樣的
情況發(fā)生,一是把一個(gè)方法分解成多個(gè),每一個(gè)方法只要求使用這些參數(shù)的一個(gè)子集;二是創(chuàng)建輔助類,用來保存參數(shù)的聚集,這些輔助類的狀態(tài)通常是靜態(tài)的。
對(duì)于參數(shù)類型,優(yōu)先使用接口而不是類。
這樣做的目的是避免影響效能的拷貝操作。
謹(jǐn)慎的使用函數(shù)對(duì)象。
創(chuàng)建函數(shù)對(duì)象最容易的方法莫過于使用匿名類,但是那樣會(huì)帶來語法上混亂,并且與內(nèi)聯(lián)的控制結(jié)構(gòu)相比,這樣也會(huì)導(dǎo)致功能上的局限性。
?
Item 26:謹(jǐn)慎的使用重載
到底是什么造成了重載機(jī)制的混淆算法,這是個(gè)爭論的話題,一個(gè)安全而保守的方法是,永遠(yuǎn)不要導(dǎo)出兩個(gè)具有相同參數(shù)數(shù)目的重載方法。而對(duì)于構(gòu)造函數(shù)來說,一個(gè)類的多個(gè)構(gòu)造函數(shù)總是重載的,在某些情況下,我們可以選擇靜態(tài)工廠,但是對(duì)于構(gòu)造函數(shù)來說這樣做并不總是切合實(shí)際的。
當(dāng)涉及到構(gòu)造函數(shù)時(shí),遵循這條建議也許是不可能的,但我們應(yīng)該極力避免下面的情形:
同一組參數(shù)只需要經(jīng)過類型的轉(zhuǎn)換就可以傳遞給不同的重載方法。如果這樣做也不能避免的話,我們至少要保證一點(diǎn):當(dāng)傳遞同樣的參數(shù)時(shí),所有的重載方法行為一致。如果不能做到這一點(diǎn),程序員就不能有效的使用方法或者構(gòu)造函數(shù)。
?
Item 27:返回零長度的數(shù)組而不是null
因?yàn)檫@樣做的原因是編寫客戶程序的程序員可能忘記寫這種專門的代碼來處理null返回值。沒有理由從一個(gè)取數(shù)組值的方法中返回null,而不是返回一個(gè)零長度數(shù)組。
?
Item 28:為所有導(dǎo)出的API元素編寫文檔注釋
不愛寫注釋可能是大多數(shù)程序員新手的通病(包括偶哈~),但是如果想要一個(gè)API真正可用,就必須寫一個(gè)文檔來說明它,保持代碼和文檔的同步是一件比較煩瑣的事情,JAVA語言環(huán)境提供了javadoc工具,從而使這個(gè)煩瑣的過程變得容易,這個(gè)工具可以根據(jù)源代碼自動(dòng)產(chǎn)生API文檔。
為了正確得編寫API文檔,我們必須每一個(gè)被導(dǎo)出的類,接口,構(gòu)造函數(shù),方法和域聲明之前加一個(gè)文檔注釋。
每一個(gè)方法的文檔注釋應(yīng)該見解的描述它和客戶之間的約定。
我們接下來討論一下Java語言的細(xì)節(jié),包括局部變量的處理,庫的使用,以及兩種不是語言本身提供的機(jī)制的使用等等一些大家平時(shí)可能忽略的問題。
?
Item 29:將局部變量的作用域最小化
和C語言要求局部變量必須被生命在代碼的開始處相比,Java程
序設(shè)計(jì)語言寬松得多,它允許你在代碼的任何位置聲明。要想使一個(gè)局部變量的作用域最小化,最高小的技術(shù)是在第一次需要使用它的地方聲明,變量的作用域是從
聲明它的地方開始到這個(gè)聲明做在的代碼塊的結(jié)束位止,如果我們把變量的聲明和代碼的使用位置分開的過大,那么對(duì)于讀這段代碼的人來說,是很不幸的。
我們幾乎都是在一個(gè)局部變量聲明的地方同時(shí)給它初始化,注意這是很重要的,甚至有時(shí)候,如果我們的初始化應(yīng)該推遲到下一個(gè)代碼的位置,我們同時(shí)應(yīng)該把聲明也往后延遲。這條規(guī)則唯一的例外是try-catch這個(gè)語句,因?yàn)槿绻粋€(gè)變量被方法初始化,那么這個(gè)方法很有可能拋出一個(gè)異常,那我們最常用的方法就是把它置于try塊的內(nèi)部去進(jìn)行初始化。由此我們可以得出,for循環(huán)優(yōu)于while循環(huán),我們?cè)谀苁褂?/span>for循環(huán)的地方盡量使用for而不使用while,因?yàn)?/span>for循環(huán)是完全獨(dú)立的,所以重用循環(huán)變量名字不會(huì)有任何傷害。
最后我們要記住的是盡量把我們的函數(shù)寫的小而集中,這樣才能真正組做到”最小化局部變量的作用域”這一要旨。
?
Item 30:了解和使用庫
使用標(biāo)準(zhǔn)庫,我們可以充分利用編寫這些庫的Java專家的知識(shí),以及在你之前其他人的使用經(jīng)驗(yàn),這就是所謂站在巨人的肩膀上看世界吧~
在每一個(gè)Java平臺(tái)的發(fā)行版本里面,都會(huì)有許多新的包的加入,和這些更新保持一直是值得的,比如說我們J2ME的開發(fā),在MIDP 1.0的時(shí)代,我們要寫個(gè)Game還要自己動(dòng)手寫工具類,現(xiàn)在MIDP2.0推出之后,大多數(shù)寫游戲的人都覺得方便了很多,因?yàn)樵谶@個(gè)版本里面加入了游戲包,為我們的開發(fā)節(jié)省了大量的人力物力。
?
???? Item 31:如果想要知道精確的答案,就要避免使用double和float
???? 對(duì)于金融行業(yè)來說,對(duì)數(shù)據(jù)的嚴(yán)整性要求是很高的,不容半點(diǎn)馬虎,那大家都知道再我們的Java語言里面有兩個(gè)浮點(diǎn)數(shù)類型的變量float和double,可能大家會(huì)認(rèn)為他們的精度對(duì)于金融行業(yè)這樣對(duì)數(shù)字敏感的行業(yè)來說,已經(jīng)夠用了,但是在開發(fā)當(dāng)中,我們要盡量少使用double和float,因?yàn)樽屗麄兙_的表達(dá)0.1是不可能的。那我們?nèi)绾谓鉀Q這個(gè)問題呢,答案是使用BigDecimal,int或者long進(jìn)行貨幣計(jì)算。在這里對(duì)大家的忠告是:對(duì)于商務(wù)運(yùn)算,我們盡量使用BigDecimal,對(duì)于性能要求較高的地方,我們有能力自己處理十進(jìn)制的小數(shù)點(diǎn),數(shù)值不太大的時(shí)候,我們可以使用int或者long,根據(jù)自己的需要來判定具體使用哪一個(gè),如果范圍超過了18位數(shù),那我們必須使用BigDecimal。
?
??? ?Item 32:如果其他類型更適合,則盡量避免使用字符串
???? 在偶看到這條建議之前,我就很喜歡用字符串,不管在什么場(chǎng)合下,先String了再說,但是實(shí)際上很多情況下,我們要根據(jù)實(shí)際情況來判定到底使用什么類型,而且字符串不適合替代枚舉類型,類型安全枚舉類型和int值
都比字符串更適合用來表示枚舉類型的常量。字符串也不適合替代聚集類型,有一個(gè)更好的方法就是簡單的寫一個(gè)類來描述這個(gè)數(shù)據(jù)集,通常是一個(gè)私有的靜態(tài)成員
類最好。字符串也不適合代替能力表,總而言之,如果可以適合更加適合的數(shù)據(jù)類型,或者可以編寫更加適當(dāng)?shù)臄?shù)據(jù)類型,那么應(yīng)該避免使用字符串來表示對(duì)象。
?
Item 33:了解字符串的連接功能
我們經(jīng)常在使用System.out.println()的時(shí)候,往括號(hào)里寫一串用“+”連接起來的字符串,這是我們最常見的,但是這個(gè)方法并不適合規(guī)模較大的情形,為連接N個(gè)字符串而重復(fù)地使用字符串連接操作符,要求N的平方級(jí)的時(shí)間,這是因?yàn)樽址欠强勺兊模@就導(dǎo)致了在字符串進(jìn)行連接的時(shí)候,前后兩者都要拷貝,這個(gè)時(shí)候我們就提倡使用StingBuffer替代String。
?
Item 34:通過接口引用對(duì)象
通俗的說就是盡量優(yōu)先使用接口而不是類來引用對(duì)象,如果有合適的接口存在那么對(duì)使用參數(shù),返回值,變量域都應(yīng)該使用接口類型養(yǎng)成使用接口作為對(duì)象的習(xí)慣,會(huì)使程序變得更加靈活。
如果沒有合適的接口,那么,用類而不是接口來引用一個(gè)對(duì)象,是完全合適的。
?
Item 35:接口優(yōu)先于映像機(jī)制
java.lang.relect提供了“通過程序來訪問關(guān)于已裝載的類的信息”,由此,我們可以通過一個(gè)給定的Class實(shí)例,獲得Constructor,Method和Field實(shí)例。
映像機(jī)制允許一個(gè)類使用另一個(gè)類,即使當(dāng)前編譯的時(shí)候后者還不存在,但是這種能力也要付出代價(jià):
我們損失了了編譯時(shí)類型檢查的好處,而且要求執(zhí)行映像訪問的代碼非常笨拙和冗長,并且在性能上大大損失。
通常,普通應(yīng)用在運(yùn)行時(shí)刻不應(yīng)以映像方式訪問對(duì)象。
?
Item 36:謹(jǐn)慎的使用本地方法
JNI允許Java應(yīng)用程序調(diào)用本地方法,所謂本地方法是指用本地程序設(shè)計(jì)語言(如C,C++)來編寫的特殊方法,本地方法可以在本地語言執(zhí)行任何計(jì)算任務(wù),然后返回到Java程序設(shè)計(jì)語言中。但是隨著JDK1.3及后續(xù)版本的推出這種通過使用本地方法來提高性能的方法已不值得提倡,因?yàn)楝F(xiàn)在的JVM越來越快了,而且使用本地方法有一些嚴(yán)重的缺點(diǎn),比如使Java原本引以為傲的安全性蕩然無存,總之在使用本地方法的時(shí)候要三思。
?
Item 37:謹(jǐn)慎使用優(yōu)化
不要因?yàn)樾阅芏鵂奚侠淼拇a結(jié)構(gòu),努力編寫好的程序而不是快的程序,但是避免那些限制性能的設(shè)計(jì)決定,同時(shí)考慮自己設(shè)計(jì)的API決定的性能后果,為了獲得更好的性能而對(duì)API進(jìn)行修改這也是一個(gè)非常不好的想法,通常我們?cè)谧鰞?yōu)化之后,都應(yīng)該對(duì)優(yōu)化的程度進(jìn)行一些測(cè)量。
?
Item 38:遵守普遍接受的命名慣例
Java有一套比較完善的命名慣例機(jī)制,大部分包含在《The Java Language Specification》,嚴(yán)格得講這些慣例分成兩類,字面的和語法的。
字面涉及包,類,接口,方法和域,語法的命名慣例比較靈活,所以爭議更大,字面慣例是非常直接和明確的,而語法慣例則相對(duì)復(fù)雜,也很松散。但是有一個(gè)公認(rèn)的做法是:“如果長期養(yǎng)成的習(xí)慣用法與此不同的話,請(qǐng)不要盲目遵從
Item 12:把類和成員的可訪問范圍降到最低
????好的模塊設(shè)計(jì)應(yīng)該盡最大可能封裝好自己的內(nèi)部信息,這樣可以把模塊之間的耦合程度降到最低。開發(fā)得以并行,無疑這將加快開發(fā)的速度,便于系統(tǒng)地維護(hù)。Java中通過訪問控制符來解決這個(gè)問題。
- public表示這個(gè)類在任何范圍都可用。
- protected表示只有子類和包內(nèi)的類可以使用
- private-package(default)表示在包內(nèi)可用
- private表示只有類內(nèi)才可以用
你
在設(shè)計(jì)一個(gè)類的時(shí)候應(yīng)該盡量的按照4321得順序設(shè)計(jì)。如果一個(gè)類只是被另一個(gè)類使用,那么應(yīng)該考慮把它設(shè)計(jì)成這個(gè)類的內(nèi)部類。通常public的類不應(yīng)
該有public得字段,不過我們通常會(huì)用一個(gè)類來定義所有的常量,這是允許的。不過必須保證這些字段要么是基本數(shù)據(jù)類型要么引用指向的對(duì)象是不可修改
的。不然他們將可能被修改。例如下面的定義中data就是不合理的,后面兩個(gè)沒有問題。
public class Con
{
??????public static final int[] data = {1,2,3};// it is bad
??????public static final String hello = "world";
??????public static final int i = 1;
}
Item 13:不可修改的類更受青睞
????不可修改的類意思是他們一經(jīng)創(chuàng)建就不會(huì)改變,例如String類。他們的設(shè)計(jì)、實(shí)現(xiàn)都很方便,安全性高——它們是線程安全的。設(shè)計(jì)不可修改類有幾點(diǎn)規(guī)則:
- 不要提供任何可以修改對(duì)象的方法
- 確保沒有方法能夠被覆蓋,可以通過把它聲明為final
- 所有字段設(shè)計(jì)成final
- 所有字段設(shè)計(jì)成private
- 確保外部不能訪問到類的可修改的組件
不可修改類也有個(gè)缺點(diǎn)就是創(chuàng)建不同值得類的時(shí)候要?jiǎng)?chuàng)建不同的對(duì)象,String就是這樣的。通常有個(gè)解決的辦法就是提供一個(gè)幫助類來彌補(bǔ),例如StringBuffer類。
Item 14:化合(合成)比繼承更值得考慮
??????
實(shí)現(xiàn)代碼重用最重要的辦法就是繼承,但是繼承破壞了封裝,導(dǎo)致軟件的鍵壯性不足。如果子類繼承了父類,那么它從父類繼承的方法就依賴父類的實(shí)現(xiàn),一旦他改
變了會(huì)導(dǎo)致不可預(yù)測(cè)的結(jié)果。作者介紹了InstrumentedHashSet作為反例進(jìn)行說明,原因就是沒有明白父類的方法實(shí)現(xiàn)。作者給出的解決辦法是
通過化合來代替繼承,用包裝類和轉(zhuǎn)發(fā)方法來解決問題。把想擴(kuò)展的類作為本類的一個(gè)private
final得成員變量。把方法參數(shù)傳遞給這個(gè)成員變量并得到返回值。這樣做的缺點(diǎn)是這樣的類不適合回掉框架。繼承雖然好,我們卻不應(yīng)該濫用,只有我們能確
定它們之間是is-a得關(guān)系的時(shí)候才使用。
Item 15:如果要用繼承那么設(shè)計(jì)以及文檔都要有質(zhì)量保證,否則就不要用它
????為了避免繼承帶來的問題,你必須提供精確的文檔來說明覆蓋相關(guān)方法可能出現(xiàn)的問題。在構(gòu)造器內(nèi)千萬不要調(diào)用可以被覆蓋的方法,因?yàn)樽宇惛采w方法的時(shí)候會(huì)出現(xiàn)問題。
import java.util.*;
public class SubClass extends SuperClass
{
?private final Date date;
?
?public SubClass()
?{
??date = new Date();?
?}
?
?public void m()
?{
??System.out.println(date);?
?}
?
?public static void main(String[] args)
?{
??SubClass s = new SubClass();
??s.m();?
?}
?
}
class SuperClass
{
?public SuperClass()
?{
??m();?
?}?
?
?public void m()
?{
??
?}
}
由于在date被初始化之前super()已經(jīng)被調(diào)用了,所以第一次輸出null而不是當(dāng)前的時(shí)間。
由于在Clone()或者序列化的時(shí)候非常類似構(gòu)造器的功能,因此readObject()和clone()方法內(nèi)最好也不要包括能被覆蓋的方法。
Item 16:在接口和抽象類之間優(yōu)先選擇前者
??????接口和抽象類都用來實(shí)現(xiàn)多態(tài),不過我們應(yīng)該優(yōu)先考慮用接口。知道嗎?James說過如果要讓他重新設(shè)計(jì)java的話他會(huì)把所有都設(shè)計(jì)成接口的。抽象類的優(yōu)點(diǎn)是方便擴(kuò)展,因?yàn)樗潜焕^承的,并且方法可以在抽象類內(nèi)實(shí)現(xiàn),接口則不行。
Item 17:接口只應(yīng)該用來定義類型
??????接口可以這樣用的 Collection c = new xxxx();這是我們最常用的。不要把接口用來做其他的事情,比如常量的定義。你應(yīng)該定義一個(gè)類,里面包含public final static 得字段。
Item 18: 在靜態(tài)和非靜態(tài)內(nèi)部類之間選擇前者
??????如果一個(gè)類被定義在其他的類內(nèi)部那么它就是嵌套類,可以分為靜態(tài)內(nèi)部類、非靜態(tài)內(nèi)部類和匿名類。
???static
member class 得目的是為enclosing class服務(wù),如果還有其他的目的,就應(yīng)該把它設(shè)計(jì)成top-level
class。nonstatic member class是和enclosing class
instance關(guān)聯(lián)的,如果不需要訪問enclosing class
instance的話應(yīng)該把它設(shè)計(jì)成static得,不然會(huì)浪費(fèi)時(shí)間和空間。anonymous
class是聲明和初始化同時(shí)進(jìn)行的。可以放在代碼的任意位置。典型應(yīng)用是Listener 和process object例如Thread。
??? 由于以前學(xué)過C語言,所以對(duì)C還是蠻有感情,而JAVA和C又有很多相似之處,很多從C轉(zhuǎn)過來學(xué)習(xí)JAVA的兄弟,可能一開始都不是很適應(yīng),因?yàn)楹芏嘣?/span>C里面的結(jié)構(gòu)在JAVA里面都不能使用了,所以下面我們來介紹一下C語言結(jié)構(gòu)的替代。
?????
????? Item 19:用類代替結(jié)構(gòu)
????? JAVA剛面世的時(shí)候,很多C程序員都認(rèn)為用類來代替結(jié)構(gòu)現(xiàn)在太復(fù)雜,代價(jià)太大了,但是實(shí)際上,如果一個(gè)JAVA的類退化到只包含一個(gè)數(shù)據(jù)域的話,這樣的類與C語言的結(jié)構(gòu)大致是等價(jià)的。
????? 比方說下面兩個(gè)程序片段:
????? class Point
????? {
?????? private float x;
?????? private float y;
????? }
????? 實(shí)際上這段代碼和C語言的結(jié)構(gòu)基本上沒什么區(qū)別,但是這段代碼恐怕是眾多OO設(shè)計(jì)Fans所不齒的,因?yàn)樗鼪]有體現(xiàn)封裝的優(yōu)異性,沒有體現(xiàn)面向?qū)ο笤O(shè)計(jì)的優(yōu)點(diǎn),當(dāng)一個(gè)域被修改的時(shí)候,你不可能再采取任何輔助的措施了,那我們?cè)賮砜匆豢床捎冒接杏蚝凸灿性L問方法的OO設(shè)計(jì)代碼段:
????? class Point
????? {
?????? private float x;
?????? private float y;
?????? public Point(float x,float y)
?????? {
???????????? this.x=x;
???????????? this.y=y;
?????? }
??????? public float getX(){retrun x;}
??????? public float getY(){return y;}
??????? public void setX(float x){this.x=x;}
??????? public void setY(float y){this.y=y;}
????? }
?
??????? 單從表面上看,這段代碼比上面那個(gè)多了很多行,還多了很多函數(shù),但是仔細(xì)想一下,這樣的OO設(shè)計(jì),似乎更人性化,我們可以方面的對(duì)值域進(jìn)行提取,修改等操作,而不直接和值域發(fā)生關(guān)系,這樣的代碼不僅讓人容易讀懂,而且很安全,還吸取了面向?qū)ο蟪绦蛟O(shè)計(jì)的靈活性,試想一下,如果一個(gè)共有類暴露它的值域,那么想要在將來的版本中進(jìn)行修改是impossible的,因?yàn)楣灿蓄惖目蛻舸a已經(jīng)遍布各處了。
需要提醒一點(diǎn)的是,如果一個(gè)類是包級(jí)私有的,或者是一個(gè)私有的嵌套類,則直接暴露其值域并無不妥之處。
?
Item 20:用類層次來代替聯(lián)合
我們?cè)谟?/span>C語言來進(jìn)行開發(fā)的時(shí)候,經(jīng)常會(huì)用到聯(lián)合這個(gè)概念,比如:
?????? typedef struct{
???? double length;
???? double width;????
}rectangleDimensions_t;
那我們?cè)?/span>JAVA里面沒有聯(lián)合這個(gè)概念,那我們用什么呢?對(duì)!用繼承,這也是JAVA最吸引我的地方之一,它可以使用更好的機(jī)制來定義耽擱數(shù)據(jù)類型,在Bruce Eckel的Thinking in java里面也多次提到了一個(gè)和形狀有關(guān)的例子,我們可以先籠統(tǒng)的定義一個(gè)抽象類,即我們通常所指的超類,每個(gè)操作定義一個(gè)抽象的方法,其行為取決于標(biāo)簽的值,如果還有其他的操作不依賴于標(biāo)簽的值,則把操作變成根類(繼承的類)中的具體方法。
這樣做的最重要的優(yōu)點(diǎn)是:類層次提供了類型的安全性。
其次代碼非常明了,這也是OO設(shè)計(jì)的優(yōu)點(diǎn)。
而且它很容易擴(kuò)展,即使是面向多個(gè)方面的工作,能夠同樣勝任。
最后它可以反映這些類型之間本質(zhì)上的層次關(guān)系,從而允許更強(qiáng)的靈活性,以便編譯時(shí)類型檢查。
?
Item 21:用類來代替enum結(jié)構(gòu)
Java程序設(shè)計(jì)語言提出了類型安全枚舉的模式來替代enum結(jié)構(gòu),它的基本思想很簡單:定義一個(gè)類來代表枚舉類型的單個(gè)元素,并且不提供任何公有的構(gòu)造函數(shù),相反,提供公有靜態(tài)final類,使枚舉類型中的每一個(gè)常量都對(duì)應(yīng)一個(gè)域。
類型安全枚舉類型的一個(gè)缺點(diǎn)是,裝載枚舉類的和構(gòu)造常量對(duì)象時(shí),需要一定的時(shí)間和空間開銷,除非是在資源很受限制的設(shè)備比如蜂窩電哈和烤面包機(jī)上,否則在實(shí)際中這個(gè)問題不會(huì)被考慮。
?總之,類型安全枚舉類型明顯優(yōu)于int類型,除非實(shí)在一個(gè)枚舉類型主要被用做一個(gè)集合元素,或者主要用在一個(gè)資源非常不受限的環(huán)境下,否則類型安全枚舉類型的缺點(diǎn)都不成問題,依次,在要求使用一個(gè)枚舉類型的環(huán)境下,我們首先應(yīng)考慮類型安全枚舉類型模式。
?
Item 22:用類和接口來代替函數(shù)指針
眾所周知,JAVA語言和C的最大區(qū)別在于,前者去掉了指針,小生第一次接觸JAVA的時(shí)候覺得好不習(xí)慣,因?yàn)橥蝗灰幌伦記]了指針,覺得好不方面啊,C語言的精髓在于其指針的運(yùn)用,而JAVA卻把它砍掉了,讓人好生郁悶,不過隨著時(shí)間的推移,我漸漸明白了用類和接口的應(yīng)用也同樣可以提供同樣的功能,我們可以直接定義一個(gè)這樣一個(gè)類,他的方法是執(zhí)行其他方法上的操作,如果一個(gè)類僅僅是導(dǎo)出這樣一個(gè)方法,那么它實(shí)際上就是一個(gè)指向該方法的指針,舉個(gè)例子:
?class StringLengthComprator{
public int compare(String s1,String s2)
{
return s1.length()-s2.length();
}
}
這
個(gè)類導(dǎo)出一個(gè)帶兩個(gè)字符串的方法,它是一個(gè)用于字符串比較的具體策略。它是無狀態(tài)的,沒有域,所以,這個(gè)類的所有實(shí)例在功能上都是等價(jià)的,可以節(jié)省不必要
的對(duì)象創(chuàng)建開銷。但是我們不好直接把這個(gè)類傳遞給可戶使用,因?yàn)榭蓱魺o法傳遞任何其他的比較策略。相反,我們可以定義一個(gè)接口,即我們?cè)谠O(shè)計(jì)具體策略類的
時(shí)候還需要定義一個(gè)策略接口:
????? public interface Comparator{
?????????? public int compare(Object o1,Object o2);
}
? 我們完全可以依照自己的需要來定義它。
具體的策略類往往使用匿名類聲明。
在JAVA中,我們?yōu)榱藢?shí)現(xiàn)指針的模式,聲明一個(gè)接口來表示該策略,并且為每個(gè)具體策略聲明一個(gè)實(shí)現(xiàn)了該接口的類,如果一個(gè)具體策略只被使用一次的話,那么通常使用匿名類來聲明和實(shí)例化這個(gè)具體策略類,如果一個(gè)策略類反復(fù)使用,那么它的類通常是一個(gè)私有的的靜態(tài)成員類。
下面我們來討論一下有關(guān)方法設(shè)計(jì)的幾個(gè)方面,下面說的幾個(gè)要點(diǎn)大多數(shù)都是應(yīng)用在構(gòu)造函數(shù)中,當(dāng)然也使用于普通方法,我們追求的依然是程序的可用性,健壯性和靈活性。
?
Item 23:檢查參數(shù)的有效性
非公有的方法我們應(yīng)該用斷言的方法來檢查它的參數(shù),而不是使用通常大家所熟悉的檢查語句來檢測(cè)。如果我們使用的開發(fā)平臺(tái)是JDK1.4或者更高級(jí)的平臺(tái),我們可以使用assert結(jié)構(gòu);否則我們應(yīng)該使用一種臨時(shí)的斷言機(jī)制。
有些參數(shù)在使用過程中是先保存起來,然后在使用的時(shí)候再進(jìn)行調(diào)用,構(gòu)造函數(shù)正是這種類型的一種體現(xiàn),所以我們通常對(duì)構(gòu)造函數(shù)參數(shù)的有效性檢查是非常仔細(xì)的。
?
Item 24:需要時(shí)使用保護(hù)性拷貝
眾所周知,JAVA在代碼安全性方面較C/C++有顯著的提高,緩沖區(qū)溢出,數(shù)組越界,非法指針等等,我們的JAVA都有一個(gè)很完善的機(jī)制來進(jìn)行免疫,但是這并不代表我們不必去考慮JAVA的安全性,即便在安全的語言,如果不采取措施,還是無法使自己與其他類隔開。假設(shè)類的客戶會(huì)盡一切手段來破壞這個(gè)類的約束條件,在這樣的前提下,你必須從保護(hù)性的方面來考慮設(shè)計(jì)程序。通過大量的程序代碼研究我們得出這樣的結(jié)論:對(duì)于構(gòu)造性函數(shù)的每個(gè)可變參數(shù)進(jìn)行保護(hù)性拷貝是必要的。需要注意的是,保護(hù)性拷貝是在檢查參數(shù)的有效性之前進(jìn)行的,并且有效性檢查是針對(duì)拷貝之后的對(duì)象,而不是原始的對(duì)象。對(duì)于“參數(shù)類型可以被不可信方子類化”的情況,不要用clone方法來進(jìn)行參數(shù)的保護(hù)性拷貝。
對(duì)
于參數(shù)的保護(hù)性拷貝并不僅僅在于非可變類,當(dāng)我們編寫一個(gè)函數(shù)或者一個(gè)構(gòu)造函數(shù)的時(shí)候,如果它要接受客戶提供的對(duì)象,允許該對(duì)象進(jìn)入到內(nèi)部數(shù)據(jù)結(jié)構(gòu)中,則
有必要考慮一下,客戶提供的對(duì)象是否是可變的,如果是,則要考慮其變化的范圍是否在你的程序所能容納的范圍內(nèi),如果不是,則要對(duì)對(duì)象進(jìn)行保護(hù)性拷貝,并且
讓拷貝之后的對(duì)象而不是原始對(duì)象進(jìn)入到數(shù)據(jù)結(jié)構(gòu)中去。當(dāng)然最好的解決方法是使用非可變的對(duì)象作為你的對(duì)象內(nèi)部足見,這樣你就可以不必關(guān)心保護(hù)性拷貝問題
了。):
?
Item 25:謹(jǐn)慎使用設(shè)計(jì)方法的原型
(1)謹(jǐn)慎的選擇方法的名字:即要注意首先要是易于理解的,其次還要與該包中的其他方法的命名風(fēng)格相一致,最后當(dāng)然要注意取一個(gè)大眾所認(rèn)可的名字。
(2)
不要追求提供便利的方法:每一個(gè)方法都應(yīng)該提供其應(yīng)具備的功能點(diǎn),對(duì)于接口和類來方法不要過多,否則會(huì)對(duì)學(xué)習(xí)使用維護(hù)等等方面帶來許多不必要的麻煩,對(duì)于
每一個(gè)類型所支持的每一個(gè)動(dòng)作,都提供一個(gè)功能完全的方法,只有一個(gè)方法過于頻繁的使用時(shí),才考慮為它提供一個(gè)快捷方法。
(3)
避免過長的參數(shù)列表:通常在實(shí)踐中,我們以三個(gè)參數(shù)作為最大值,參數(shù)越少越好,類型相同的長參數(shù)列尤其影響客戶的使用,兩個(gè)方法可以避免過長的參數(shù)這樣的
情況發(fā)生,一是把一個(gè)方法分解成多個(gè),每一個(gè)方法只要求使用這些參數(shù)的一個(gè)子集;二是創(chuàng)建輔助類,用來保存參數(shù)的聚集,這些輔助類的狀態(tài)通常是靜態(tài)的。
對(duì)于參數(shù)類型,優(yōu)先使用接口而不是類。
這樣做的目的是避免影響效能的拷貝操作。
謹(jǐn)慎的使用函數(shù)對(duì)象。
創(chuàng)建函數(shù)對(duì)象最容易的方法莫過于使用匿名類,但是那樣會(huì)帶來語法上混亂,并且與內(nèi)聯(lián)的控制結(jié)構(gòu)相比,這樣也會(huì)導(dǎo)致功能上的局限性。
?
Item 26:謹(jǐn)慎的使用重載
到底是什么造成了重載機(jī)制的混淆算法,這是個(gè)爭論的話題,一個(gè)安全而保守的方法是,永遠(yuǎn)不要導(dǎo)出兩個(gè)具有相同參數(shù)數(shù)目的重載方法。而對(duì)于構(gòu)造函數(shù)來說,一個(gè)類的多個(gè)構(gòu)造函數(shù)總是重載的,在某些情況下,我們可以選擇靜態(tài)工廠,但是對(duì)于構(gòu)造函數(shù)來說這樣做并不總是切合實(shí)際的。
當(dāng)涉及到構(gòu)造函數(shù)時(shí),遵循這條建議也許是不可能的,但我們應(yīng)該極力避免下面的情形:
同一組參數(shù)只需要經(jīng)過類型的轉(zhuǎn)換就可以傳遞給不同的重載方法。如果這樣做也不能避免的話,我們至少要保證一點(diǎn):當(dāng)傳遞同樣的參數(shù)時(shí),所有的重載方法行為一致。如果不能做到這一點(diǎn),程序員就不能有效的使用方法或者構(gòu)造函數(shù)。
?
Item 27:返回零長度的數(shù)組而不是null
因?yàn)檫@樣做的原因是編寫客戶程序的程序員可能忘記寫這種專門的代碼來處理null返回值。沒有理由從一個(gè)取數(shù)組值的方法中返回null,而不是返回一個(gè)零長度數(shù)組。
?
Item 28:為所有導(dǎo)出的API元素編寫文檔注釋
不愛寫注釋可能是大多數(shù)程序員新手的通病(包括偶哈~),但是如果想要一個(gè)API真正可用,就必須寫一個(gè)文檔來說明它,保持代碼和文檔的同步是一件比較煩瑣的事情,JAVA語言環(huán)境提供了javadoc工具,從而使這個(gè)煩瑣的過程變得容易,這個(gè)工具可以根據(jù)源代碼自動(dòng)產(chǎn)生API文檔。
為了正確得編寫API文檔,我們必須每一個(gè)被導(dǎo)出的類,接口,構(gòu)造函數(shù),方法和域聲明之前加一個(gè)文檔注釋。
每一個(gè)方法的文檔注釋應(yīng)該見解的描述它和客戶之間的約定。
我們接下來討論一下Java語言的細(xì)節(jié),包括局部變量的處理,庫的使用,以及兩種不是語言本身提供的機(jī)制的使用等等一些大家平時(shí)可能忽略的問題。
?
Item 29:將局部變量的作用域最小化
和C語言要求局部變量必須被生命在代碼的開始處相比,Java程
序設(shè)計(jì)語言寬松得多,它允許你在代碼的任何位置聲明。要想使一個(gè)局部變量的作用域最小化,最高小的技術(shù)是在第一次需要使用它的地方聲明,變量的作用域是從
聲明它的地方開始到這個(gè)聲明做在的代碼塊的結(jié)束位止,如果我們把變量的聲明和代碼的使用位置分開的過大,那么對(duì)于讀這段代碼的人來說,是很不幸的。
我們幾乎都是在一個(gè)局部變量聲明的地方同時(shí)給它初始化,注意這是很重要的,甚至有時(shí)候,如果我們的初始化應(yīng)該推遲到下一個(gè)代碼的位置,我們同時(shí)應(yīng)該把聲明也往后延遲。這條規(guī)則唯一的例外是try-catch這個(gè)語句,因?yàn)槿绻粋€(gè)變量被方法初始化,那么這個(gè)方法很有可能拋出一個(gè)異常,那我們最常用的方法就是把它置于try塊的內(nèi)部去進(jìn)行初始化。由此我們可以得出,for循環(huán)優(yōu)于while循環(huán),我們?cè)谀苁褂?/span>for循環(huán)的地方盡量使用for而不使用while,因?yàn)?/span>for循環(huán)是完全獨(dú)立的,所以重用循環(huán)變量名字不會(huì)有任何傷害。
最后我們要記住的是盡量把我們的函數(shù)寫的小而集中,這樣才能真正組做到”最小化局部變量的作用域”這一要旨。
?
Item 30:了解和使用庫
使用標(biāo)準(zhǔn)庫,我們可以充分利用編寫這些庫的Java專家的知識(shí),以及在你之前其他人的使用經(jīng)驗(yàn),這就是所謂站在巨人的肩膀上看世界吧~
在每一個(gè)Java平臺(tái)的發(fā)行版本里面,都會(huì)有許多新的包的加入,和這些更新保持一直是值得的,比如說我們J2ME的開發(fā),在MIDP 1.0的時(shí)代,我們要寫個(gè)Game還要自己動(dòng)手寫工具類,現(xiàn)在MIDP2.0推出之后,大多數(shù)寫游戲的人都覺得方便了很多,因?yàn)樵谶@個(gè)版本里面加入了游戲包,為我們的開發(fā)節(jié)省了大量的人力物力。
?
???? Item 31:如果想要知道精確的答案,就要避免使用double和float
???? 對(duì)于金融行業(yè)來說,對(duì)數(shù)據(jù)的嚴(yán)整性要求是很高的,不容半點(diǎn)馬虎,那大家都知道再我們的Java語言里面有兩個(gè)浮點(diǎn)數(shù)類型的變量float和double,可能大家會(huì)認(rèn)為他們的精度對(duì)于金融行業(yè)這樣對(duì)數(shù)字敏感的行業(yè)來說,已經(jīng)夠用了,但是在開發(fā)當(dāng)中,我們要盡量少使用double和float,因?yàn)樽屗麄兙_的表達(dá)0.1是不可能的。那我們?nèi)绾谓鉀Q這個(gè)問題呢,答案是使用BigDecimal,int或者long進(jìn)行貨幣計(jì)算。在這里對(duì)大家的忠告是:對(duì)于商務(wù)運(yùn)算,我們盡量使用BigDecimal,對(duì)于性能要求較高的地方,我們有能力自己處理十進(jìn)制的小數(shù)點(diǎn),數(shù)值不太大的時(shí)候,我們可以使用int或者long,根據(jù)自己的需要來判定具體使用哪一個(gè),如果范圍超過了18位數(shù),那我們必須使用BigDecimal。
?
??? ?Item 32:如果其他類型更適合,則盡量避免使用字符串
???? 在偶看到這條建議之前,我就很喜歡用字符串,不管在什么場(chǎng)合下,先String了再說,但是實(shí)際上很多情況下,我們要根據(jù)實(shí)際情況來判定到底使用什么類型,而且字符串不適合替代枚舉類型,類型安全枚舉類型和int值
都比字符串更適合用來表示枚舉類型的常量。字符串也不適合替代聚集類型,有一個(gè)更好的方法就是簡單的寫一個(gè)類來描述這個(gè)數(shù)據(jù)集,通常是一個(gè)私有的靜態(tài)成員
類最好。字符串也不適合代替能力表,總而言之,如果可以適合更加適合的數(shù)據(jù)類型,或者可以編寫更加適當(dāng)?shù)臄?shù)據(jù)類型,那么應(yīng)該避免使用字符串來表示對(duì)象。
?
Item 33:了解字符串的連接功能
我們經(jīng)常在使用System.out.println()的時(shí)候,往括號(hào)里寫一串用“+”連接起來的字符串,這是我們最常見的,但是這個(gè)方法并不適合規(guī)模較大的情形,為連接N個(gè)字符串而重復(fù)地使用字符串連接操作符,要求N的平方級(jí)的時(shí)間,這是因?yàn)樽址欠强勺兊模@就導(dǎo)致了在字符串進(jìn)行連接的時(shí)候,前后兩者都要拷貝,這個(gè)時(shí)候我們就提倡使用StingBuffer替代String。
?
Item 34:通過接口引用對(duì)象
通俗的說就是盡量優(yōu)先使用接口而不是類來引用對(duì)象,如果有合適的接口存在那么對(duì)使用參數(shù),返回值,變量域都應(yīng)該使用接口類型養(yǎng)成使用接口作為對(duì)象的習(xí)慣,會(huì)使程序變得更加靈活。
如果沒有合適的接口,那么,用類而不是接口來引用一個(gè)對(duì)象,是完全合適的。
?
Item 35:接口優(yōu)先于映像機(jī)制
java.lang.relect提供了“通過程序來訪問關(guān)于已裝載的類的信息”,由此,我們可以通過一個(gè)給定的Class實(shí)例,獲得Constructor,Method和Field實(shí)例。
映像機(jī)制允許一個(gè)類使用另一個(gè)類,即使當(dāng)前編譯的時(shí)候后者還不存在,但是這種能力也要付出代價(jià):
我們損失了了編譯時(shí)類型檢查的好處,而且要求執(zhí)行映像訪問的代碼非常笨拙和冗長,并且在性能上大大損失。
通常,普通應(yīng)用在運(yùn)行時(shí)刻不應(yīng)以映像方式訪問對(duì)象。
?
Item 36:謹(jǐn)慎的使用本地方法
JNI允許Java應(yīng)用程序調(diào)用本地方法,所謂本地方法是指用本地程序設(shè)計(jì)語言(如C,C++)來編寫的特殊方法,本地方法可以在本地語言執(zhí)行任何計(jì)算任務(wù),然后返回到Java程序設(shè)計(jì)語言中。但是隨著JDK1.3及后續(xù)版本的推出這種通過使用本地方法來提高性能的方法已不值得提倡,因?yàn)楝F(xiàn)在的JVM越來越快了,而且使用本地方法有一些嚴(yán)重的缺點(diǎn),比如使Java原本引以為傲的安全性蕩然無存,總之在使用本地方法的時(shí)候要三思。
?
Item 37:謹(jǐn)慎使用優(yōu)化
不要因?yàn)樾阅芏鵂奚侠淼拇a結(jié)構(gòu),努力編寫好的程序而不是快的程序,但是避免那些限制性能的設(shè)計(jì)決定,同時(shí)考慮自己設(shè)計(jì)的API決定的性能后果,為了獲得更好的性能而對(duì)API進(jìn)行修改這也是一個(gè)非常不好的想法,通常我們?cè)谧鰞?yōu)化之后,都應(yīng)該對(duì)優(yōu)化的程度進(jìn)行一些測(cè)量。
?
Item 38:遵守普遍接受的命名慣例
Java有一套比較完善的命名慣例機(jī)制,大部分包含在《The Java Language Specification》,嚴(yán)格得講這些慣例分成兩類,字面的和語法的。
字面涉及包,類,接口,方法和域,語法的命名慣例比較靈活,所以爭議更大,字面慣例是非常直接和明確的,而語法慣例則相對(duì)復(fù)雜,也很松散。但是有一個(gè)公認(rèn)的做法是:“如果長期養(yǎng)成的習(xí)慣用法與此不同的話,請(qǐng)不要盲目遵從