love fish大鵬一曰同風(fēng)起,扶搖直上九萬里

          常用鏈接

          統(tǒng)計

          積分與排名

          friends

          link

          最新評論

          Effective Java(轉(zhuǎn))

          第一條:
          內(nèi)容:靜態(tài)工廠替代構(gòu)造函數(shù)
          例子:String.valueOf()?? getInstance()等
          靜態(tài)工廠方法優(yōu)點:
          1,可以隨意起名字,更好的描述返回對象
          2,每次調(diào)用的時候不一定要創(chuàng)建一個新對象
          3,可以返回一個原返回類型的子類型對象
          靜態(tài)工廠方法的缺點:
          1,如果類沒有公有或者受保護(hù)的構(gòu)造函數(shù)就不能被子類化
          2,不符合規(guī)范,破壞規(guī)范。在API文檔中不會被那么明確的標(biāo)識出來。

          第二條:使用私有構(gòu)造函數(shù)強(qiáng)化單態(tài)
          單態(tài)的模式大家都知道了,但是使用單態(tài)的時候記住要使用私有的構(gòu)造函數(shù)。
          原因很簡單,如果不如此很難保證單態(tài)。只要new一下另一個對象就生成了

          第三條:有些類是不能實例化的,如果你要做到這點,記得使用私有的構(gòu)造函數(shù)。
          例如:java.util.Collections??????? java.lang.Math 等

          第四條:避免創(chuàng)造重復(fù)的對象
          特別是這樣的代碼不要寫: String str = new String("a string");
          因為這樣每次執(zhí)行的時候都創(chuàng)建了一個"a string"對象。
          可以寫成這樣:String str = "a string?";
          另外順便說一句,這個時候你再次定義String? str2 = "a string";會復(fù)用上邊的字符串.

          第五條:在有些情況下手動消除對象的引用
          public?class?Stack{
          ????
          private?Object[]?elements;
          ????
          private?int?size?=?0;
          ????
          public?Stack(int?initialCapacity){
          ????????
          this.elements?=?new?Object[initialCapacity];
          ????}

          ????
          public?void?push(Object?e){
          ????????ensureCapacity();
          ????????elements[size
          ++]?=?e;
          ????}

          ????
          public?Object?pop(){
          ????????
          if?(size?==?0)
          ????????????
          throw?new?EmptyStackException();
          ????????
          return?elements[--size];
          ????}

          ????
          private?void?ensureCapacity(){
          ????????
          if?(elements.length?==?size){
          ????????????Object[]?oldElements?
          =?elements;
          ????????????elements?
          =?new?Object[2*element.length+1];
          ????????System.arraycopy(oldElements,
          0,elements,0,size);
          ????????}

          ????}

          }
          如果這個Stack先大量增長,然后收縮,然后在比較小的范圍內(nèi)使用,必定造成大量的不可回收的對象,造成內(nèi)存泄漏.。
          解決辦法:改造一下pop()方法
          public?Object?pop(){
          ???????
          if(size?=?=?0)
          ???????????
          throw?new?EmptyStackException();
          ??????? Object?result?
          =?elements[--size];
          ?????????????????//加上這一句
          ?????? ?elements.[size]
          =null;
          ????? ??
          return?result;
          }
          六,避免使用終結(jié)(finalizer)函數(shù)
          原因:終結(jié)函數(shù)通常是不可預(yù)測的,一般情況下不要使用,使用的結(jié)果會帶來很多問題,不穩(wěn)定,性能差,移植問題等等。
          分析原因:
          1,從一個對象不可到達(dá)到它的終結(jié)函數(shù)被執(zhí)行,這段時間是任意的,不確定的,所以時間關(guān)鍵的系統(tǒng)不應(yīng)該使用終結(jié)函數(shù)。
          2,及時的執(zhí)行終結(jié)函數(shù)是垃圾回收算法決定的,這種算法在不同的JVM實現(xiàn)中會大相徑庭,使用終結(jié)函數(shù)的結(jié)果就是導(dǎo)致移植性問題
          3,如果執(zhí)行終結(jié)函數(shù)的線程一直低于當(dāng)前的線程的優(yōu)先級,很可能造成占用大量內(nèi)存,極端情況是出現(xiàn)OutOfMemoryError
          4,JSL不保證終結(jié)函數(shù)一定被執(zhí)行,所以不要依賴終結(jié)函數(shù)來更新關(guān)鍵性的永久狀態(tài),例如數(shù)據(jù)庫的永久鎖
          5,不要相信System.gc()??? System.runFinalization這兩個函數(shù),它們只能提高終結(jié)函數(shù)的執(zhí)行機(jī)會,并不保證一定執(zhí)行。唯一保證一定執(zhí)行的是System.runFinalizersOnExit喝Runtime.runFinalizersONExit()但這兩個方法已經(jīng)被聲明不建議使用.
          6,一個在終結(jié)函數(shù)中的一場不會打出任何信息
          七:在改寫equals方法的時候遵守通用約定
          分析:
          1,有些情況下不要隨意改寫equals
          (1),一個類的每個實例本質(zhì)上是唯一的,例如Thread
          (2),不管新一個類是否提供了“邏輯相等”的測試功能,例如java.util.Random
          (3),超類已經(jīng)改寫了equals,從超類繼承過來的行為對于子類也是適合的 例如Set從AbstractSet繼承了equals
          (4),一個類是私有的,或者是包級私有的,并且確定它的equals方法永遠(yuǎn)不會被調(diào)用
          2, 通用的約定
          自反性:? 對于任意的引用值x ,x.equals(x)一定為true
          對稱性:? 對于任意的引用值x,y? x.equals(y)返回true是 y.equals(x)也一定返回true
          傳遞性:對于任意的引用值x,y,z? 如果x.equals(y)返回true?并且y.equals(z)返回true 那么 x.equals(z)也一定是true
          一致性:對于任意的x,y如果x,y沒有被更改,調(diào)用任意多次x.equals(y)返回的結(jié)果應(yīng)該一樣。
          非空性:對任意的引用x ,x.equals(null)一定返回false
          3不要將equals聲明中的Object對象換成別的類型
          4,不要讓equals方法依賴不可靠資源

          八:改寫equals方法時總要改寫hashCode
          原因:來自java.lang.Object關(guān)于hashCode的規(guī)范
          1,在一個應(yīng)用執(zhí)行期間,如果一個對象的equals方法比較所用到的信息沒有修改的話,那么對該對象調(diào)用hashCode多次,比如如一的返回同一個數(shù)
          2,如果兩個對象的equals方法返回true,那么分別調(diào)用hashCode方法返回的值應(yīng)該相等
          3,在兩個兌現(xiàn)的equals方法返回false時,盡可能的讓hashCode方法返回的值不相等,提高散列表的性能
          分析:如果改寫了equals沒有改寫hashCode在使用map等集合類的時候會出現(xiàn)問題。
          九:盡可能的改寫toString方法,并在顯示內(nèi)容中盡可能的包括令人感興趣的信息。并且在注釋中表示出你的意圖。
          十:謹(jǐn)慎的改寫clone方法,改寫前考慮淺拷貝和全拷貝
          十一:考慮實現(xiàn)Comparable接口,如果你的對象要排序,那么記得實現(xiàn)這個方法
          十二:使類和成員的可訪問能力最小化,
          十三:支持非可變性
          非可變性遵循以下的原則:
          1,不提供任何改變對象的方法
          2,保證沒有可被子類改寫的方法
          3,保證所有的域都使final
          4,使所有的域都成為私有的
          5,保證任何可變組件互斥訪問
          6,非可變對象本質(zhì)是線程安全的,不需要同步

          有些內(nèi)容已經(jīng)是老生長嘆,所以只是列舉一下,不再詳細(xì)說明
          十四:復(fù)合優(yōu)于繼承(Think in java中有不少說明)
          十五:要們專門為繼承而設(shè)計,并給出文檔說明,要么禁止繼承
          十六:接口優(yōu)于抽象類(參考一下GOF的設(shè)計模式)
          十七:接口只是被定義類型,不要試圖使用常量接口
          十八:優(yōu)先考慮靜態(tài)成員類
          說明:嵌套類有四種
          靜態(tài)成員類?, 非靜態(tài)成員類,?? 匿名類,? 局部類? 除了第一種之外,其它三種都被稱為內(nèi)部類
          1,靜態(tài)成員類是一種最簡單的嵌套類,最好把它看成一個普通類,只是碰巧被聲明在另一個類內(nèi)部而已,
          它可以訪問外圍類的所有成員,包括那些生民為私有的成員。靜態(tài)成員類是外圍類的一個靜態(tài)成員,也遵守同樣的可訪問性規(guī)則,如果它被聲明為私有的,那么它只能在外圍類內(nèi)部可以訪問。靜態(tài)成員類的一個用法是公有的輔助類。例如HashMap的? static class Entry

          非靜態(tài)成員類和靜態(tài)成員類的區(qū)別主要是非靜態(tài)成員類需要一個外圍類實例的引用,如果你不需要訪問外圍類的實例的話,記得使用靜態(tài)成員類。

          匿名類被使用的相對多一些,但是大量的使用匿名類會讓你的代碼比較亂,作過GUI開發(fā)的人多會有所感觸。并且記住,盡可能的讓你的匿名類短小。

          局部類,局部類的使用是最少的,很少會使用到這個,如果用到記得使局部類盡可能的短小

          對于C語言用戶的部分
          十九:用類代替結(jié)構(gòu)
          二十:用類層次代替聯(lián)合
          二十一:用類來代替enum,但是在jdk1.5的時候提供了enum的支持,有些東西不一樣了
          二十二:用類和接口代替函數(shù)指針
          二十三、在函數(shù)的開始檢查參數(shù)的有效性
          如果函數(shù)對參數(shù)有要求,例如不接受Null ,不接受負(fù)數(shù)等等,應(yīng)該盡可能在函數(shù)的最開始給出校驗,如果發(fā)現(xiàn)錯誤拋出異常
          二十四、在需要的時候使用保護(hù)性拷貝
          1,假設(shè)類的客戶會盡一切手段來破壞這個類的約束條件,在這樣的前提下,你必須保護(hù)性的設(shè)計程序。
          2,實例
          import?java.util.Date;

          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;
          ????}

          ????
          //.other?code
          }

          //這個函數(shù)看似沒有問題,實際上存在著漏洞,如下使用方法
          Date?start?=?new?Date();
          Date?end?
          =?new?Date();
          Period?p?
          =?new?Period(start,end);
          //如果加上這句,檢驗就失效了。
          end.setYear(78);

          //為了對應(yīng)這個問題,更改構(gòu)造函數(shù):

          public?Period(Date?start,Date?end){
          ????
          this.start?=?new?Date(start.getTime());
          ????
          this.end?=?new?Date(end.getTime());
          ????
          if?(start.compareTo(end)>0){
          ????????
          throw?new?IllegalArgumentException(start+"after"+end);
          ????}

          }
          注意,拷貝要在檢驗之前進(jìn)行
          3,參數(shù)類型可以被不可信任方子類化的情形,清不要使用clone方法進(jìn)行參數(shù)的保護(hù)化拷貝
          二十五、謹(jǐn)慎的設(shè)計方法的原型
          1,謹(jǐn)慎的選擇方法的名字,一個好的方法名字可以讓人很快記住
          2,不要過于追求提供便利的方法,如果方法太多會增加使用者的學(xué)習(xí)負(fù)擔(dān),只有當(dāng)一個操作被頻繁使用的時候再添加一個對應(yīng)的方法。
          3,避免太長的參數(shù)列表,盡量讓你的參數(shù)不大于三個
          4,對于參數(shù)類型,優(yōu)先使用接口,而不是類。
          原因:如果使用接口,你可以隨意的替換實現(xiàn),或者同時存在多個實現(xiàn)。
          使用類沒有這個優(yōu)勢。
          5,謹(jǐn)慎的使用函數(shù)對象(一個類中一堆靜態(tài)函數(shù))
          二十六、謹(jǐn)慎的使用重載
          1,實例
          import?java.util.ArrayList;
          import?java.util.Collection;
          import?java.util.HashMap;
          import?java.util.HashSet;
          import?java.util.List;
          import?java.util.Set;


          public?class?CollectionClassifier?{
          ????
          public?static?String?classify(Set?s){
          ????????
          return?"Set";
          ????}

          ????
          public?static?String?classify(List?s){
          ????????
          return?"List";
          ????}

          ????
          public?static?String?classify(Collection?s){
          ????????
          return?"Unknow?Collection";
          ????}

          ????
          ????
          public?static?void?main(String[]?args)?{
          ????????Collection[]?tests?
          =?new?Collection[]{
          ????????????
          new?HashSet(),
          ????????????
          new?ArrayList(),
          ????????????
          new?HashMap().values()
          ????????}
          ;
          ????????
          for(int?i=0;i<tests.length;i++){
          ????????????System.out.println(classify(tests[i]));
          ????????}

          ????}

          }
          結(jié)果是打印出三個unknown
          這個程序的行為是違反直覺的,對弈重載方法的選擇是靜態(tài)的,而對于被改寫的方法的選擇是動態(tài)的
          (這個可以參考我的另一篇文章)
          2,盡量不要使用兩個參數(shù)數(shù)目相同的重載方法
          如以下兩個重載函數(shù):
          test1(String name,String value)
          test1(String name,String[] value)
          當(dāng)你調(diào)用test1("name",null)的時候就出錯了。
          二十七、使用零長度數(shù)組代替Null作為返回值
          原因:返回Null會造成使用者每次使用的時候都要作一次判斷,但有人會說返回一個零長度數(shù)組會產(chǎn)生new的開銷,不如Null性能好。這個不是一定的,因為我們可以這樣來作
          private final static Cheese[]? NULL_CHESE_ARRAY = new Cheese[0];
          每次需要的時候返回這個數(shù)組就好了。

          二十八、為所有的導(dǎo)出Api元素編寫文檔注釋
          二十九、使一個局部變量的作用域最小化,最好的辦法使在第一次使用的時候聲明
          1,幾乎每一個局部變量的聲明都應(yīng)該包含一個初始化表達(dá)式,如果你還沒有足夠的信息來初始化那就推遲聲明。
          2,for循環(huán)優(yōu)先于while循環(huán),見下邊的例子
          //????????for循環(huán)
          ????????for(Iterator?ie?=?list.iterator();ie.hasNext()){
          ????????????doSomething(ie.next());
          ????????}

          //????????while循環(huán)
          ????????
          ????????Iterator?ie1?
          =?list1.iterator();
          ????????
          while(ie1.hasNext()){
          ????????????doSomething(ie1.next());
          ????????}

          ????????Iterator?ie2?
          =?list2.iterator();
          ????????
          while(ie1.hasNext()){?????//bug
          ????????????doSomething(ie2.next());
          ????????}
          這個問題源于復(fù)制粘貼,在編碼的過程中復(fù)制粘貼幾乎是不可避免的,使用for循環(huán)當(dāng)你出錯的時候可以在編譯器發(fā)生錯誤,而使用while則不會發(fā)現(xiàn)。盡早發(fā)現(xiàn)錯誤總是好的。
          三十、了解和使用庫(產(chǎn)生隨機(jī)數(shù))
          詳細(xì):如果你希望產(chǎn)生一個位于0-某個上界的隨機(jī)數(shù),大多數(shù)的人的寫法如下
          static?Random?rnd?=?new?Random();
          ????
          static?int?random(int?n){
          ????????
          return?Math.abs(rnd.nextInt())%n;
          ????}
          這個方法存在三個缺點:
          缺點一:
          如果n是一個比較小的2的乘方 那么經(jīng)過一段相當(dāng)短的周期后它產(chǎn)生的隨即數(shù)序列將會重復(fù)
          缺點二:
          如果n不是2的乘方,那么平均起來某些數(shù)比另外一些數(shù)出現(xiàn)的更為頻繁,如果n比較大則這個問題更加顯著如果產(chǎn)生100萬范圍內(nèi)的隨機(jī)數(shù),你會發(fā)現(xiàn)數(shù)字幾乎全部在0-666 666 ,前2/3的數(shù)字
          缺點三:
          在有些情況下會災(zāi)難性失敗,返回一個落在范圍之外的數(shù)字。原因是使用了Math.abs來得到一個非負(fù)數(shù)。
          如果nextInt()返回 Integer.MIN_VALUE,那么abs后也會返回Integer.MIN_VALUE ,假設(shè)n不是2的乘方,那么取模操作符%將返回一個負(fù)數(shù),這幾乎肯定造成你的程序失敗,而且這個失敗很難重現(xiàn)。

          為了編寫一個避免上邊三個缺點的random,你必須了解線性同于偽隨機(jī)發(fā)生器、數(shù)論、和2的求補運算知識。不過Jdk已經(jīng)實現(xiàn)了一個現(xiàn)成的可以使用,那就是Random.nextInt(int)

          這一段很多比較簡單,簡單羅列一下,部分重要的做了解釋
          三十一、如果要求精確的答案,盡量避免使用float 和double,這個可以參照我的一片文章
          貨幣尤其不合適。可以使用BigDecimal代替
          三十二、如果其它類型更適合,盡量避免使用字符串
          1,字符串不能替代其它的值類型
          2,字符串不適合代替枚舉類型
          3,字符串不適合代替聚集類型
          4,字符串也不是和代替能力表
          因為有些時候,使用字符串會大大降低性能
          三十三、了解字符串連接的性能
          說明:使用StringBuffer代替 +來連接字符串
          三十四、通過接口來引用對象,這能讓你的程序更加靈活
          三十五、接口優(yōu)先于反射。
          使用反射會帶來很多問題,例如:
          1,不能編譯期發(fā)現(xiàn)錯誤
          2,代碼混亂
          3,調(diào)試?yán)щy
          4,性能損失。
          除非必須,否則不使用反射
          三十六、謹(jǐn)慎的使用本地方法JNI
          三十七、謹(jǐn)慎的進(jìn)行優(yōu)化,有三條優(yōu)化格言:
          1,很多計算上的過失都被歸咎于效率原因(沒有獲得必要的效率),而不是其它的原因--甚至包括盲目的作傻事.?? ---William A.Wulf [Wulf72]
          2,不要去計較一些小的效率上的得失,在97%的情況下,不成熟的優(yōu)化是一切問題的根源。
          ????????????------Donald E.Knuth[Knuth74]
          3,在優(yōu)化方面要遵守兩個原則:
          規(guī)則一:不要做優(yōu)化
          規(guī)則二:還是不要做優(yōu)化--也就是說,在你還沒有絕對清晰的未優(yōu)化方案前,請不要優(yōu)化。
          ??????????? -----M.A.Jackson[Jackson75]
          每次試圖做優(yōu)化之前和之后請對性能進(jìn)行測試
          三十八:遵守普遍接受的命名規(guī)則
          三十九:值針對不正常的條件才使用異常,也就是說不要在正常的情況下使用異常來控制流程,活著解決某些已知的問題。因為會大量的損失性能
          四十、對于可恢復(fù)的條件使用被檢查的異常,對于程序錯誤使用運行時異常
          詳細(xì):Java提供了三種可拋出結(jié)構(gòu),checked Exception,? run-time exception , error
          什么時候使用什么很容易讓人混淆,下邊是一個簡單的區(qū)分原則
          1,如果期望調(diào)用者能夠恢復(fù),那么對于這樣的條件應(yīng)該使用被檢查異常
          2,你所實現(xiàn)的所有未檢查的拋出結(jié)構(gòu)都是run time exception ,而不是Error
          四十一:避免不必要的使用被檢查異常
          四十二:盡可能的使用標(biāo)準(zhǔn)異常,例如IllegalArgumentException ,NullPointerException ,IndexOutOfBoundsException等等
          四十三:拋出異常要適合于相應(yīng)的抽象。
          高層實現(xiàn)應(yīng)該捕獲異常,同時拋出一個可以按照高層抽象解釋的異常(業(yè)務(wù)邏輯上符合高層邏輯),這種做法叫做異常轉(zhuǎn)譯
          四十四:每個方法拋出的異常都應(yīng)改有文檔
          四十五:在細(xì)節(jié)消息中包含失敗-捕獲信息
          詳細(xì):在異常字符串中應(yīng)包含有用的信息,例如IndexOutOfBoundsException異常的細(xì)節(jié)消息應(yīng)該包括下界、上界以及沒有落在其中的實際下標(biāo)

          因為有些被使用比較少,只簡單列舉了一下
          四十六、努力使失敗保持原子性
          1,一般而言,一個失敗的方法調(diào)用應(yīng)該使對象保持"它在被調(diào)用之前的狀態(tài)",具有這種屬性的方法被稱為具有
          失敗原子性。
          四十七、不要忽略異常
          例如這樣的代碼是錯誤的
          try{
          ?//.....
          }catch(SomeException e){
          }
          四十八、對共享可變數(shù)據(jù)要同步訪問
          注:Java的雙重檢查模式并不保證一定正確
          四十九、避免過多的同步
          1,為了避免死鎖的危險,在一個被同步的方法或者代碼中,永遠(yuǎn)不要放棄對客戶的控制。
          2,通常同步區(qū)域內(nèi)應(yīng)該做盡可能少的工作
          五十、永遠(yuǎn)不要在循環(huán)外部調(diào)用wait
          五十一、不要依賴于線程調(diào)度器,因為這可能造成不可移植性
          1,不要使用Thread.yield來修正程序,因為在不同的Jdk中,不能移植
          2,線程優(yōu)先級是Java平臺上最不可移植的特征了。
          3,對大多數(shù)程序員來說,Thread.yield的唯一用途是在測試期間人為的增加一個程序的并發(fā)行。
          五十二、線程安全性的文檔
          一個類為了被多個線程安全的使用,必須在文檔中姓儲的說明它所支持的線程安全級別。
          1,非可變。
          2,線程安全的。
          3,有條件的線程安全
          4,線程兼容的
          5,線程對立的。
          五十三、避免使用線程組
          五十四、謹(jǐn)慎的實現(xiàn)Serializable
          五十五、考慮使用自定義的序列化形式
          五十六、保護(hù)的編寫readObject方法
          五十七、必要時提供一個readResolve方法

          posted on 2007-01-31 14:28 liaojiyong 閱讀(311) 評論(0)  編輯  收藏 所屬分類: Java

          主站蜘蛛池模板: 大同县| 海原县| 图们市| 洛川县| 措美县| 商丘市| 永济市| 台前县| 买车| 明光市| 崇仁县| 邵东县| 罗江县| 敦化市| 长海县| 星座| 大同县| 体育| 安平县| 汉沽区| 西乌珠穆沁旗| 陆良县| 周口市| 荆州市| 土默特左旗| 河津市| 巴林左旗| 和静县| 遂昌县| 青岛市| 关岭| 万宁市| 太康县| 甘肃省| 博爱县| 阿合奇县| 元阳县| 满城县| 农安县| 涡阳县| 信宜市|