love fish大鵬一曰同風起,扶搖直上九萬里

          常用鏈接

          統計

          積分與排名

          friends

          link

          最新評論

          Effective Java(轉)

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

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

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

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

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

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

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

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

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

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

          對于C語言用戶的部分
          十九:用類代替結構
          二十:用類層次代替聯合
          二十一:用類來代替enum,但是在jdk1.5的時候提供了enum的支持,有些東西不一樣了
          二十二:用類和接口代替函數指針
          二十三、在函數的開始檢查參數的有效性
          如果函數對參數有要求,例如不接受Null ,不接受負數等等,應該盡可能在函數的最開始給出校驗,如果發現錯誤拋出異常
          二十四、在需要的時候使用保護性拷貝
          1,假設類的客戶會盡一切手段來破壞這個類的約束條件,在這樣的前提下,你必須保護性的設計程序。
          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
          }

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

          //為了對應這個問題,更改構造函數:

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

          }
          注意,拷貝要在檢驗之前進行
          3,參數類型可以被不可信任方子類化的情形,清不要使用clone方法進行參數的保護化拷貝
          二十五、謹慎的設計方法的原型
          1,謹慎的選擇方法的名字,一個好的方法名字可以讓人很快記住
          2,不要過于追求提供便利的方法,如果方法太多會增加使用者的學習負擔,只有當一個操作被頻繁使用的時候再添加一個對應的方法。
          3,避免太長的參數列表,盡量讓你的參數不大于三個
          4,對于參數類型,優先使用接口,而不是類。
          原因:如果使用接口,你可以隨意的替換實現,或者同時存在多個實現。
          使用類沒有這個優勢。
          5,謹慎的使用函數對象(一個類中一堆靜態函數)
          二十六、謹慎的使用重載
          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]));
          ????????}

          ????}

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

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

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

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

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

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

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

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

          主站蜘蛛池模板: 东至县| 西乌珠穆沁旗| 尉氏县| 阿荣旗| 马公市| 聊城市| 保德县| 楚雄市| 久治县| 固始县| 织金县| 阿瓦提县| 赣榆县| 萍乡市| 舒城县| 缙云县| 东至县| 门源| 肃南| 昌江| 巴塘县| 黎平县| 罗江县| 南昌市| 黄陵县| 福泉市| 孙吴县| 石屏县| 临朐县| 汽车| 肇庆市| 赤城县| 都匀市| 陈巴尔虎旗| 民勤县| 乾安县| 鹿邑县| 电白县| 宝兴县| 青神县| 大新县|