乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Java高效編程

       飄飄 2007-08-07
      • 創(chuàng)建及銷毀對象
        • - 考慮用靜態(tài)工廠方法替代構(gòu)造函數(shù)
          • OK靜態(tài)工廠方法的優(yōu)勢
            • 優(yōu)先級 1靜態(tài)工廠方法的一個優(yōu)勢是,它們具有自己的名字。構(gòu)造函數(shù)的參數(shù)自身無法描述被返回的對象,而選用名字合適的靜態(tài)工廠方法可以使類的使用更加容易,產(chǎn)生的客戶代碼更容易閱讀。
            • 優(yōu)先級 2靜態(tài)工廠方法的第二個優(yōu)勢是它們不需要在每次調(diào)用時都去創(chuàng)建一個新的對象。這使得非可變類可以使用預(yù)先構(gòu)造的實(shí)例,或者在構(gòu)造階段先緩存這些實(shí)例,然后重復(fù)使用它們,從而避免創(chuàng)建不必要的重復(fù)對象。
            • 靜態(tài)工廠方法為重復(fù)調(diào)用而返回同一對象的能力,可以用來控制某一時刻實(shí)例的存在情況。
              • 有兩個理由使靜態(tài)方法可以做到這一點(diǎn)。
                • 首先它能夠使類保證實(shí)例是singleton。
                • 其次,它能夠使非可變類保證沒有兩個相等的實(shí)例同時存在。
            • 優(yōu)先級 3靜態(tài)工廠的第三個優(yōu)勢是它們可以返回到返回類型的任何子類型(subtype)對象。這使用戶在選擇返回對象的類時具有很大的靈活性。
          • Not OK靜態(tài)工廠的缺陷
            • 優(yōu)先級 1靜態(tài)工廠的主要缺陷是類在沒有公共或受保護(hù)的構(gòu)造函數(shù)時不能被子類化。例如,不可能子類化Collections Framework中的任何集合類。
            • 優(yōu)先級 2靜態(tài)工廠方法的另一個缺陷是名字之間不容易區(qū)分。
          • 聽著[我警告你]總地來說,如果你權(quán)衡過了這兩種方法,同時沒有其他因素影響你的選擇取向,那么最好還是簡單地使用構(gòu)造函數(shù),因為這符合規(guī)范。
        • - 使用私有構(gòu)造函數(shù)強(qiáng)化singleton屬性
          • singleton類就是一種只能被實(shí)例化一次的簡單類。
          • - 實(shí)現(xiàn)singlton有兩種途徑,這兩種途徑都以保持構(gòu)造函數(shù)私有及提供公共的靜態(tài)成員允許客戶訪問它的唯一實(shí)例為基礎(chǔ)。
            • 優(yōu)先級 1一種實(shí)現(xiàn)方法是,公共的靜態(tài)成員是一個final域:
              //singleton with final field
              public class Elvis {
                        public static final Elvis INSTANCE = new Elvis();  
                        private Elvis() {
                                   ...
                        }  
                        ...
              }
              私有構(gòu)造函數(shù)只被調(diào)用一次,以初始化公共的靜態(tài)final域Elvis.INSTANCE。公共的或受保護(hù)的構(gòu)造函數(shù)的缺乏,保證了一個“唯一的elvis”的世界:一旦Elvis類被初始化,僅有一個Elvis實(shí)例存在。

              OK第一種方式的突出優(yōu)點(diǎn)是類的成員的聲明使類清楚地表明它是singleton:公共的靜態(tài)域是final,所以這個域?qū)⒖偸前嗤膶ο笠谩?

              優(yōu)先級 2第二種方法中,提供了公共的靜態(tài)工廠方法,取代了公共的靜態(tài)final域:
              //singleton with static factory
              public class Elvis {
                        private static final Elvis INSTANCE = new Elvis();  
                        private Elvis() {
                                   ...
                        }
                        public static Elvis getInstance() {
                                   return INSTANCE;
                        }
              }
              對所有的靜態(tài)方法Elvis.getInstance的調(diào)用,都返回同一個對象的引用,沒有其他的Elvis實(shí)例被創(chuàng)建。

              OK第二種方式的突出優(yōu)點(diǎn)在于它給使用者提供了靈活性,當(dāng)你決定把這個類改變?yōu)榉莝ingleton的時候,無需修改API。

              聽著[我警告你]總而言之,如果你確信該類將永遠(yuǎn)是singleton的,那么應(yīng)該使用第一種方法。反之,第二種方式是更好的選擇。

        • - 用私有構(gòu)造函數(shù)強(qiáng)化不可實(shí)例化能力
          • 有些類不希望被實(shí)例化,對它們實(shí)例化也沒有意義。

            Not OK試圖通過將類抽象化來強(qiáng)化類的不可實(shí)例化能力是行不通的。這是因為類可以被子類化,而子類可以被實(shí)例化。這種做法還會誤導(dǎo)用戶,以為這種類的目的是為了實(shí)現(xiàn)繼承的。

            OK有一種簡單的方法可以解決這一問題。由于缺省的構(gòu)造函數(shù)僅在類不包含顯示的構(gòu)造函數(shù)時才會生成,我們可以在類中包含顯式的私有類型構(gòu)造函數(shù)來實(shí)現(xiàn)類的不可實(shí)例化特性。

            因為聲明的構(gòu)造函數(shù)是私有的,所以它在類的外部不可訪問。假設(shè)構(gòu)造函數(shù)不會被類自身從內(nèi)部調(diào)用,即能保證類永遠(yuǎn)不會被實(shí)例化。 例:
            class F {
                   private F() {
                           ...
                   }
                   ...
            }

        • - 避免創(chuàng)建重復(fù)對象
          • 重用同一對象比每次都創(chuàng)建功能等同的新對象通常更適合。重用方式既快速也更加時尚。

            Not OK下面的語句是個極端的例子,千萬不要這樣做:
            String s = new String("Silly"); //never do this
            該語句每次執(zhí)行時都創(chuàng)建一個新的String實(shí)例,然而這些對象的創(chuàng)建是不必要的。

            OK一個簡單的版本如下: String s = "No longer silly";

            Not OK除了對非可變對象重用,我們也可以重用可變(mutable)對象,只要知道它不會發(fā)生變化。下面是一個例子,不要使用這種做法:
            public class Person {
                   private final Date birthDate;
                   public Person(Date birthDate) {
                           this.birthDate = birthDate;
                   }

                   //Don‘t do this!
                   public boolean isBabyBoomer() {
                           Calendar gmtCal = Calender.getInstance(Timezone.getTimezone("GMT"));
                           gmtCal.set(1946, Calender.JANUARY, 1, 0, 0, 0);
                           Date boomStart = gmtCal.getTime();
                           gmtCal.set(1965, Calender.JANUARY, 1, 0, 0, 0);
                           Date boomEnd = gmtCal.getTime();
                           return birthDate.compareTo(boomStart) >= 0
                                       && birthDate.compareTo(boomEnd) <0;
                   }
            }
            在每次被調(diào)用時,方法isBabyBoomer都不必要地創(chuàng)建了一個新的Calendar、TimeZone和兩個Date實(shí)例。

            OK下面的版本通過使用靜態(tài)方法的初始化方法避免了這樣的低效做法。
            public class Person {
                   private final Date birthDate;
                   public Person(Date birthDate) {
                           this.birthDate = birthDate;
                   }
                   private static final Date BOOM_START;
                   private static final Date BOOM_END;  

                   static {
                           Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
                           gmtCal.set(1946, Calender.JANUARY, 1, 0, 0, 0);
                           Date BOOM_START = gmtCal.getTime();
                           gmtCal.set(1965, Calender.JANUARY, 1, 0, 0, 0);
                           Date BOOM_END = gmtCal.getTime();
                   }

                   public boolean isBabyBoomer() {
                           return birthDate.compareTo(boomStart) >= 0
                                           && birthDate.compareTo(boomEnd) <0;
                   }
            }

        • - 消除對過期對象的引用
          • Not OK例: //Can you spot the "memory leak"?
            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*elements.lenght+1];                System.arraycopy(OldElements,0,elements,0,size);
                           }
                   }
            }

            聽著[我警告你]這個程序沒有明顯的錯誤,但是卻隱藏著一個問題,不嚴(yán)格地講,程序有一個“內(nèi)存漏洞”。如果棧先增長再收縮,那么被棧彈出的對象即使在程序不再引用它們時,也不會被垃圾回收單元回收。這是由于棧維護(hù)著對這些對象的過期引用(obsolete reference)。所謂過期引用,是指永遠(yuǎn)不會被解除引用的引用。

            OK解決這種問題的方法很簡單:一旦它們過期,清除掉對它們的引用。
            pop正確的版本是:
            public Object pop() {
                      if(size == 0)
                                 throw new EmptyStackException();
                      Object result = elements[--size];
                      elements[size] = null;
                      return result;
            }

            OK清除掉過期引用的額外好處是,如果它們隨后被錯誤地解除引用,程序?qū)捎贜ullPointerException異常退出,而不是繼續(xù)錯誤地運(yùn)行下去。

        • - 避免使用終結(jié)程序
          • 終結(jié)程序(finalizer)的行為是不可預(yù)測的,而且是危險的,通常也不必要。不要把終結(jié)程序作為C++析構(gòu)函數(shù)(destructor)的類似物。

            終結(jié)程序無法保證能別及時的執(zhí)行,這意味著不能用終結(jié)程序來處理時間關(guān)鍵(time-critical)性的操作。例如,依賴終結(jié)程序去關(guān)閉打開的文件是一個嚴(yán)重的錯誤,因為打開的文件描述符是一種有限資源,而JVM不會及時地安排終結(jié)程序執(zhí)行,如果多個文件處于打開狀態(tài),程序有可能會因為無法再打開文件而執(zhí)行失敗。

            JLS不僅不保證終結(jié)程序的及時執(zhí)行,它甚至不保證終結(jié)程序會獲得執(zhí)行。永遠(yuǎn)不要依賴終結(jié)程序去更新關(guān)鍵的持續(xù)狀態(tài)(persistent state)。例如,依靠終結(jié)程序釋放一個共享資源如數(shù)據(jù)庫上的持續(xù)鎖,將是導(dǎo)致所有分布系統(tǒng)跨掉的絕佳方法。

      • 類和接口
        • - 最小化類和成員的可訪問能力
          • 區(qū)分設(shè)計良好與設(shè)計拙劣的模塊的唯一也是最重要的元素是模塊向外部模塊隱藏內(nèi)部數(shù)據(jù)和其他實(shí)現(xiàn)細(xì)節(jié)的程度。
          • 經(jīng)驗規(guī)則指出應(yīng)該使每個類或成員盡可能地不被外部訪問。換句話說,在設(shè)計程序時,應(yīng)該根據(jù)軟件功能,使用允許的最低的訪問等級。
          • 有一條規(guī)則限制了我們給方法降低可訪問性的能力:如果方法要重載超類中的方法,那么不允許子類中該方法的訪問等級低于超類中的訪問等級。
        • - 傾向于非可變性
          • 非可變類就是類的實(shí)例不能被修改的類。如String。

            OK非可變類有很多好的存在理由

            • 非可變類更容易設(shè)計、實(shí)現(xiàn)和使用
            • 不易犯錯誤
            • 更安全

            為了使類成為非可變的,要遵循下面5條原則

            • 優(yōu)先級 1不要提供任何修改對象的方法。
            • 優(yōu)先級 2保證沒有可以被重載的方法。這防止了粗心的或惡意的子類危害對象的不可變行為。防止方法被重載的一般方式是是類成為final的。
            • 優(yōu)先級 3使所有的域成為final。
            • 優(yōu)先級 4使所有的域都是私有的。
            • 優(yōu)先級 5保證對任何可變組件互斥訪問。

            下面是一個稍微復(fù)雜的例子:
            public final class Complex {
                   private final float re;
                   private final float im;  
                   public Complex(float re, float im) {
                           this.re = re;
                           this.im = im;
                   }
                   public float realPart()        { return re; }
                   public float imaginaryPart()        { return im; }  

                   public Complex add(Complex c) {
                           return new Complex(re+c.re,im+c.im);
                   }  
                  
                    ...  
                   
                   public boolean equals(Object o) {
                           if(o==this)
                                   return true;
                           if(!(o instanceOf Complex))
                                   return false;  
                           Complex c = (Complex) 0;
                           
                           return(Float.floatToIntBits(re) == Float.floatToIntBits(c.re) ) &&
                                           (Float.floatToIntBits(im) == Float.floatToIntBits(c.im));
                   }  
                   
                   public int hashCode() {
                           int result = 17 + Float.floatToIntBits(re);
                           result = 37*result + Float.floatToIntBits(im);
                           return result;
                   }  
                   
                   public String toString() {
                           return "("+re+" + "+im+"i)";
                    }
            }
            這個類表示復(fù)數(shù),注意到算術(shù)操作創(chuàng)建和返回一個新的復(fù)數(shù)實(shí)例,而不是修改了這個實(shí)例。

            OK非可變對象本質(zhì)上是線程安全的,不需要同步機(jī)制。這是獲得線程安全最簡單的途徑。線程不會看到其他線程對非可變對象施加的影響,因而,非可變對象可以被自由地共享。

            OK非可變對象可以提供一個靜態(tài)工廠,將經(jīng)常被請求的實(shí)例緩存起來,避免在被請求的實(shí)例存在時,重復(fù)地創(chuàng)建新的實(shí)例。BigInteger和Boolean類都具有這種靜態(tài)工廠方法。

            OK不僅可以共享非可變對象,還可以共享它們的內(nèi)部信息。

            OK非可變對象為其他對象——無論是可變還是非可變的,創(chuàng)建了大量的構(gòu)造塊。

            Not OK非可變對象的真正也是唯一的缺點(diǎn)是對每個不同的值要求一個單獨(dú)的對象。

        • - 組合優(yōu)于繼承
          • 繼承是實(shí)現(xiàn)代碼重用的有力途徑,但它不總是完成這項工作的最后的工具。與方法調(diào)用不同,繼承打破了封裝性。子類的特有的功能,依賴于它的超類的實(shí)現(xiàn)細(xì)節(jié)。超類的實(shí)現(xiàn)會隨著版本而改變,如果出現(xiàn)這樣的情況,即使不觸動子類的代碼,它也會被破壞。

            不去擴(kuò)展現(xiàn)有的類,而是給類增加一個引用現(xiàn)有類實(shí)例的新的私有域,這種設(shè)計方法被成為復(fù)合(composition),因為現(xiàn)有的類成為了新的類的一部分。

            繼承只有當(dāng)子類確實(shí)是超類的“子類型”(subtype)時,才是適合的。換句話說,對兩個類A和B,如果“B是A”的關(guān)系存在,那么B應(yīng)該擴(kuò)展A。在把B擴(kuò)展A時,問這樣一個問題:“任何的B都是A嗎?”,如果答案是否定的,那么通常應(yīng)該把A作為B的一個私有實(shí)例,然后暴露更小、更簡單的API:A不是B的基本部分,只是它的實(shí)現(xiàn)細(xì)節(jié)。

        • - 設(shè)計和文檔化繼承
          • 類必須提供文檔準(zhǔn)確地描述重載任一方法的效果。類必須說明它的可重載方法的自用性(self-use):對每個公共的或受保護(hù)的方法或構(gòu)造函數(shù),它的文檔信息都必須要表明它在調(diào)用哪一個可重載的方法、以什么順序調(diào)用及每一個調(diào)用的結(jié)果如何影響后面的處理。

            為了允許程序員有效地進(jìn)行子類化處理而不必承受不必要的痛苦,類必須用認(rèn)真選擇的受保護(hù)方法提供它內(nèi)部實(shí)現(xiàn)的鉤子(hook)。

            構(gòu)造函數(shù)一定不能調(diào)用可重載的方法,無法直接地還是間接地。超類構(gòu)造函數(shù)會在子類構(gòu)造函數(shù)之前運(yùn)行,所以子類中的重載方法會在子類構(gòu)造函數(shù)運(yùn)行之前被調(diào)用。如果重載方法依賴于由子類構(gòu)造函數(shù)執(zhí)行的初始化,那么該方法將不會按期望的方式執(zhí)行。

            Not OK例:
            public class Super { //Broken - constructor invokes overridable method
                   public Super(){
                           m();
                   }  
                   public void m() {     
                   }
            }  
            下面的子類重載了m,而m被超類唯一的構(gòu)造函數(shù)錯誤地調(diào)用了:
            final class sub extends Super {
                   private final Date date;  
                   //Blank final, set by constructor
                   Sub() {
                           date = new Date();
                   }  
                   //Overrides Super.m, invoked by the constructor Super()
                   public void m() {
                           System.out.println(date);
                   }  
                   public static void main(String[] args) {
                           Sub s = new Sub();
                           s.m();
                   }  
            }  
            它第一次打印出的是null。這是因為方法m被構(gòu)造函數(shù)Super()在構(gòu)造函數(shù)Sub()初始化date域之前被調(diào)用。

            clone和readObject方法都不能調(diào)用可重載的方法,無論是直接的還是間接的。如果確定要用來繼承的類實(shí)現(xiàn)Serializable,并且類中有一個readResolve或writeReplace方法,那么必須使readResolve或writeReplace方法成為受保護(hù)的而不是私有的。一旦這些方法是私有的,它們就將被子類悄然忽略。

            對那些不是專門設(shè)計用于安全地實(shí)現(xiàn)子類化并具有文檔說明的類,禁止子類化。

            禁止子類化的方法有兩種

            • 最容易的方法是將類聲明為final的。
            • 另一種是使所有的構(gòu)造函數(shù)私有或者成為包內(nèi)私有,并用公共的靜態(tài)工廠取代構(gòu)造函數(shù)。
        • - 接口優(yōu)于抽象類
          • Java語言為定義允許有多種實(shí)現(xiàn)的類型提供了兩種機(jī)制:接口和抽象類。

            • 兩種機(jī)制的最明顯區(qū)別是抽象類允許包含某種方法的實(shí)現(xiàn),而接口不允許。
            • 一個更重要的區(qū)別:為實(shí)現(xiàn)有抽象類定義的類型,實(shí)現(xiàn)的類必須是抽象類的子類。Java只允許單一繼承,因此抽象類作為類型的定義受到了極大的限制。

            現(xiàn)有的類可以很容易的被更新以實(shí)現(xiàn)新的接口。所有需要做的工作是增加還不存在的方法并在類的聲明中增加一個implement語句。

            接口是定義混合類型(mixins)的理想選擇。mixin是這樣的類型:除了它的“基本類型(primary type)”外,類還可以實(shí)現(xiàn)額外的類型,以表明它提供了某些可選的功能。

            接口允許非層次類型框架的構(gòu)造。對于組織某些事物,類型層次是極好的選擇,但有些事物不能清楚地組織成嚴(yán)格的層次。

            接口通過使用封裝類方式,能夠獲得安全、強(qiáng)大的功能。如果使用抽象類定義類型,那么程序員在增加功能是,除了使用繼承外別無選擇,而且得到的類與封裝類相比功能更差、更脆弱。

            盡管接口不允許方法實(shí)現(xiàn),但使用接口定義類型并不妨礙給程序員提供實(shí)現(xiàn)上的幫助??梢酝ㄟ^抽象的構(gòu)架實(shí)現(xiàn)類與希望輸出的所有重要的接口配合,從而將接口與抽象類的優(yōu)點(diǎn)組合在一起。

            使用抽象類定義具有多個實(shí)現(xiàn)的類型與使用接口相比有一個明顯優(yōu)勢:演進(jìn)抽象類比演進(jìn)接口更容易。如果在以后的版本重,需要給抽象類增加新方法,那么總可以增加一個包含正確的缺省實(shí)現(xiàn)的具體方法。此時所有現(xiàn)存的該抽象類的實(shí)現(xiàn)都會具有這個新的方法。

        • - 靜態(tài)成員優(yōu)于非靜態(tài)的
          • 嵌套類(nested class)是一種定義在其他類內(nèi)部的類。嵌套類應(yīng)該僅僅為包容它的類而存在。

            - 有四種類型嵌套類:

            • 靜態(tài)成員類(static member class)
              • 靜態(tài)成員類是最簡單的嵌套類。它最好被看做是普通的類碰巧被聲明在其他的類的內(nèi)部。它對所有封閉類中的成員有訪問權(quán)限,甚至那些私有成員。靜態(tài)成員類的一種通常用法是作為公共的輔助類,僅當(dāng)和它的外部類協(xié)作時才有意義。

                如果聲明了一個不要求訪問封閉實(shí)例的成員類,切記要在它的聲明里使用static修飾符,把成員類變?yōu)殪o態(tài)的。 私有靜態(tài)成員類的一般用法是用來表示它們的封閉類對象的組件。

            • 非靜態(tài)成員類(nonstatic member classer)
              • 每一個非靜態(tài)的成員類與它包含類的封閉實(shí)例(enclosing instance)隱式地關(guān)聯(lián)。如果嵌套類的實(shí)例可以在它的封閉類實(shí)例之外單獨(dú)存在,那么嵌套類不能成為非靜態(tài)成員類:創(chuàng)建沒有封閉實(shí)例的非靜態(tài)成員類實(shí)例是不可能的。非靜態(tài)成員實(shí)例與它的封閉實(shí)例之間的關(guān)聯(lián)在前者被創(chuàng)建時即建立,在此之后它不能被修改。

            • 匿名類(anonymous class)
              • 優(yōu)先級 1匿名類的行為與靜態(tài)的或非靜態(tài)的成員類一樣,依賴于它們出現(xiàn)的位置:如果它們出現(xiàn)在非靜態(tài)的上下文中,則具有封閉實(shí)例。匿名類僅在代碼中唯一的一點(diǎn)被實(shí)例化才能被使用,由于匿名類沒有名字,因此僅在被實(shí)例化后不需要再被訪問的情況下才適合使用。

                過長的匿名類會傷害程序的可讀性。
                //Typical use of an anonymous class
                Arrays.sort(args, new Comparator() {
                              public int compare(Object o1,Object o2) {
                                             return ((String)o1).length() - ((String)o2).length();
                              }
                }

                優(yōu)先級 2匿名類另一種通常用法是創(chuàng)建過程對象(process object),例如Thread、Runnable或者TimerTask實(shí)例。

                優(yōu)先級 3第三種常用法是在靜態(tài)工廠方法內(nèi)使用。

                優(yōu)先級 4第四種用法用在復(fù)雜的類型安全枚舉類型——要求給每個實(shí)例提供單獨(dú)的子類——的公共靜態(tài)的final域初始化程序中。

            • 局部類(local class)
              • 在局部變量可以聲明的地方都可以聲明局部類,它們同樣遵守作用域規(guī)則,性質(zhì)跟匿名類一樣。
            • 聽著[我警告你]除了第一種,其他的三種類都被稱為內(nèi)部類(inner class)

            聽著[我警告你]四種嵌套類,每種都有自己的用處。如果嵌套類在單一的方法之外可見,或是太長而不合適使用在一個方法內(nèi),那么使用成員類。如果成員類實(shí)例需要它的封閉類的引用,那么使它成為非靜態(tài)的;否則為靜態(tài)的。如果類存在于方法內(nèi)部,那么如果你僅需要在唯一一個位置創(chuàng)建實(shí)例,并且已存在刻化該類的類型,則使它成為匿名類;否則,用局部類。

      • 對象的通用方法
        • - 重載equals時要遵守通用約定
          • - 如果滿足下列條件,就不要重載equals
            • 每個類實(shí)例本質(zhì)上是唯一的。

              不關(guān)心類是否提供了“邏輯意義的等同”(logical equality)測試。例如java.util.Random本來可以重載equals方法,用以檢查兩個Random實(shí)例是否會產(chǎn)生相同的隨機(jī)數(shù)序列,但設(shè)計者不認(rèn)為客戶會需要或想要這個功能。這種情況下,使用從Object繼承的equals實(shí)現(xiàn)就夠了。

              超類已經(jīng)重載了equals,而從超類繼承的行為適合該類。

              類是私有的或包內(nèi)私有(package-private)的,而且可以確定它的equals方法永遠(yuǎn)不會被調(diào)用。

          • 當(dāng)類有邏輯上的等同意義而不僅僅是對象意義上的等同,而且超類沒有重載equals方法以實(shí)現(xiàn)期望的行為,這時才需要重載。
          • - equals方法實(shí)現(xiàn)了相等關(guān)系(equivalence relation)
            • 自反性(reflective):對于任意的引用值x,x.equals(x)總是返回true。
            • 對稱性(symmetric):對于任意的引用值x、y,如果y.equals(x)返回true,x.equals(y)總返回true。
            • 傳遞性(transitive):對于任意的引用值x、y、z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)總是返回true.
            • 一致性(consistent):對于任意的引用值x、y,如果對象中用于equals比較的信息沒有修改,那么對x.equals(y)的多個調(diào)用,要么一致為true,要么一致為false。
            • 對于任何非空引用值x,x.equals(null)總是返回false。
          • - 為實(shí)現(xiàn)高質(zhì)量的equals方法,下面提供一些方法
            • 優(yōu)先級 1用"=="操作符檢查是否參數(shù)是對該對象的引用。

              優(yōu)先級 2用instanceof操作符檢查是否參數(shù)是正確的類型。
              public boolean equals(Object o) {
                      if(!(o instanceof SomeClass))
                               return false;
                      ...
              }

              優(yōu)先級 3把參數(shù)映射到正確的類型。

              優(yōu)先級 4對類中每一個“主要的”(significant)域,檢查是否參數(shù)中的域與對象中的相應(yīng)的域匹配。

              優(yōu)先級 5完成equals方法時,問自己3個問題:它是否是對稱的、傳遞的、一致的。

          • - 實(shí)現(xiàn)equals方法應(yīng)該注意的地方
            • 在重載equal方法時要重載hashCode方法。

              不要使自己聰明過頭。把任何的同義形式考慮在比較的范圍內(nèi)一般是糟糕的想法,例如File類不應(yīng)該與指向同一文件的符號鏈接進(jìn)行比較,實(shí)際上File類也沒有這樣做。

              不要設(shè)計依賴于不可靠資源的equals方法。

              不要將equals聲明中的Object替換為其他類型。程序員編寫出形如下面所示的equals方法并不少見,它會讓人摸不清頭腦:所設(shè)計方法為什么不能正確工作:
              public boolean equals(Myclass o) {
                     ...
              }
              問題出在這個方法沒有重載(override)參數(shù)為Object類型的Object.equals方法。而是過載(overload)了它。這在正常的equals方法中,又提供了一個“強(qiáng)類型”的equals方法。

        • - 重載equals時永遠(yuǎn)要重載hashCode
          • 一定要在每一個重載了equals的類中重載hashCode方法。不這樣做會違背Object.hashCode的一般約定,并導(dǎo)致你的類與所有基于散列的集合一起作用時不能正常工作,這些集合包括HashMap、HashSet和Hashtable。

            不重載hashCode方法違背了java.lang.Object的規(guī)范:相等的對象必須有相等的散列碼。兩個截然不同的實(shí)例根據(jù)類的equals方法也許邏輯上是等同的,但對于Object類的hashCode方法,它們就是兩個對象,僅此而已。因而對象的hashCode方法返回兩個看上去是隨機(jī)的數(shù)值,而不是約定中要求的相等的值。

            好的hash函數(shù)傾向于為不相等的對象生成不相等的hash碼。理想的情況下,hash函數(shù)應(yīng)該把所有不相等的實(shí)例的合理集合均一地分布到所有可能的hash值上去。達(dá)到理想狀態(tài)很難,但是下面有一種相對合適的方法

            • 優(yōu)先級 11.保存某個非0常數(shù)如17,到名為result的int類型變量中
            • 優(yōu)先級 22.對對象中每個“主要域”f,(每個域由equals方法負(fù)責(zé)),做下面的工作
              • a.為域計算int型的hash碼c
                • i.如果域是boolean型,計算(f?0:1)。

                  ii.如果域是byte型、char型、short型或int型,計算(int)f。

                  iii.如果域是long型,計算(int)(f^(f>>>32))。

                  iv.如果域是float型,計算Float.floattoIntBits(f)。

                  v.如果域是double型,計算Double.doubleToLongBits(f),然后如2.a.iii所示,對long型結(jié)果進(jìn)一步處理。

                  vi.如果域是對象引用,而且這個類的equals方法又遞歸地調(diào)用了equals方法對域進(jìn)行比較,那么對這個域遞歸地調(diào)用hashCode方法。如果需要一種更復(fù)雜的比較方式,那么先為這個域計算出“范式表示”,然后在該“范式表示”上調(diào)用hashCode方法。如果域為null,則返回0。

                  vii.如果域是數(shù)組,則把每個元素作為分離的域?qū)Υ?。即遞歸地使用這些規(guī)則,為每個“主要元素”計算hash碼。然后用2.b所示方法復(fù)合這些值。

              • b.把步驟a中計算出的hash碼c按如下方式與result復(fù)合: result = 37*result + c;
            • 優(yōu)先級 33.返回result。
            • 優(yōu)先級 44.完成hashCode方法后,測試是否相同的實(shí)例會有相同的hash碼,如果不是,找到原因,修正問題。
        • - 永遠(yuǎn)要重載toString
          • 為類提供一個好的toString實(shí)現(xiàn)可以使類使用起來更加賞心悅目。
          • 實(shí)際使用中,toString方法應(yīng)該返回包含在對象中的所有令人感興趣的信息。
          • 無論是否指明格式,都要把你的意圖清楚地文檔化出來。
        • - 謹(jǐn)慎的重載clone
          • 為了實(shí)現(xiàn)Cloneable接口,會產(chǎn)生一種古怪的機(jī)制:不通過調(diào)用構(gòu)造函數(shù)卻創(chuàng)建了一個對象。
          • 實(shí)現(xiàn)對像拷貝的精巧的方法是提供一個拷貝構(gòu)造函數(shù)(copy constructor)??截悩?gòu)造函數(shù)及它的靜態(tài)工廠變形與Cloneable/clone方法相比有很多好處
            • 它們不依賴于那種有風(fēng)險的蹩腳的對象創(chuàng)建機(jī)制;
            • 不需要遵守由糟糕的文檔規(guī)范的規(guī)約;
            • 不會與final域的正常使用產(chǎn)生沖突;
            • 不要求客戶不必要地捕獲被檢查的異常;
            • 給客戶提供了一種類型化的對象。
      • 方法
        • - 檢查參數(shù)的有效性
          • 如果方法沒有對參數(shù)做檢查,會出現(xiàn)幾種情形。

            • 方法可能在執(zhí)行中間失敗退出并給出含糊的異常。
            • 更差的是,方法能正常返回,并計算了錯誤結(jié)果。

            對那些不被方法使用但會被保存以供使用的參數(shù),檢查有效性尤為重要。

            一種重要的例外是有效性檢查開銷高,或者不切實(shí)際,而且這種有效性檢查在計算的過程中會被隱式地處理的情形。

            總的來說,每次在設(shè)計方法或設(shè)計構(gòu)造函數(shù)時,要考慮它們的參數(shù)有什么限制。要在文檔中注釋出這些限制,并在方法體的開頭通過顯示的檢查,對它們進(jìn)行強(qiáng)化。養(yǎng)成這樣的習(xí)慣是重要的,有效性檢查所需要的不多的工作會從它的好處中得到補(bǔ)償。

        • - 使用保護(hù)性拷貝
          • 必須在客戶會使用一切手段破壞類的約束的前提下,保護(hù)性地設(shè)計程序。

            優(yōu)先級 1Not OK下面的類的目的是表示非可變的時間周期:
            //Broken "immutable" time period class
            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;
                     }

                     public Date start() {
                               return start;
                     }
                     public Date end() {
                               return end;
                     }

                     ...
            }
            乍看上去,這個類是非可變的,并通過執(zhí)行周期的起始點(diǎn)不會落在周期終止點(diǎn)之后的判斷,增強(qiáng)了類的約束。然而,如果Date是可變的,這種約束很容易被違背:
            // Attack the internals of a Period instance
            Date start = new Date();
            Date end = new Date();
            Period p = new Period(start,end);
            end.setYear(78);  //Modifies internals of p!

            為了保護(hù)Period實(shí)例的內(nèi)部細(xì)節(jié)免于這種攻擊,對構(gòu)造函數(shù)的每一個可變參數(shù)使用保護(hù)性拷貝是必要的。

            優(yōu)先級 1OK使用副本代替初始值作為Period實(shí)例的組件:
            //Repaired constructor - make defensice copies of parameters
            public Period(Date start, Date end){
                     this.start = new Date(start.getTime());
                     this.end = new Date(end.getTime());
                     if(this.start.compareTo(this.end)>0)
                               throw new IllegalArgumentException(start+" after "+end):
            }

            保護(hù)性拷貝要在參數(shù)的有效性檢查的前面,并且有效性檢測要在副本而不是初值上執(zhí)行。

            優(yōu)先級 2Not OK盡管替代構(gòu)造函數(shù)成功防止了前面的攻擊,但改變一個Period實(shí)例仍然是可能的,因為訪問器對它的可變的內(nèi)部細(xì)節(jié)提供了訪問能力。
            //Second attack on the internals of a Period instance
            Date start = new Date();
            Date end = new Date();
            Period p = new Period(start,end);
            p.end().setYear(78);          //Modifies internals of p!

            優(yōu)先級 2OK為了防止第二種攻擊,簡單地修改訪問器,返回可變內(nèi)部域的保護(hù)性拷貝即可:
            //Repair accessors - make defensive copies of internal fields
            public Date start() {
                     return (Date) start.clone();
            }
            public Date end() {
                     return (Date) end.clone();
            }

            聽著[我警告你]只要可能,就應(yīng)該使用非可變對象作為對象的組件,以便不再關(guān)心保護(hù)性拷貝問題。

        • - 認(rèn)真設(shè)計方法簽名
          • 認(rèn)真地給方法選名字。名字要永遠(yuǎn)遵守標(biāo)準(zhǔn)的命名慣例。
          • 不要過于追求提供便利的方法。對接口這一點(diǎn)是千真萬確的,接口中方法太多會使實(shí)現(xiàn)程序和用戶使用時變得復(fù)雜。
          • 避免長參數(shù)列表。三個參數(shù)的使用就該作為最大值。類型相同的長參數(shù)序列尤其有害。
            • 優(yōu)先級 1有兩種技術(shù)可以大幅縮短常參數(shù)列表。一種技術(shù)是將一個方法分成多個方法實(shí)現(xiàn),其中每個方法僅需要參數(shù)類表的一個子集。
            • 優(yōu)先級 2第二種技術(shù)是創(chuàng)建助手類,用來保持參數(shù)集合。
          • 在參數(shù)類型的使用上,接口優(yōu)于類。
          • 謹(jǐn)慎地使用函數(shù)對象。
        • - 謹(jǐn)慎地使用過載
          • Not OK下面是一個意圖良好的嘗試,按照是否是集、列表還是其他種類的集合把集合分類:
            //Broken - incorrect user of overloading!
            public class CollectionClassifier {
                     public static String classify(Set s) {
                               return "Set";
                     }
                     public static String classify(List l) {
                               return "List";
                     }
                     public static String classify(Collection c) {
                               return "Unkown Collection";
                     }
                     public static void main(String[] args) {
                               Collection[] tests = new Collection[] {
                                         new HashSet(),
                                         new ArrayList(),
                                         new HaspMap().values()
                               };

                               for(int i=0;i<tests.length;i++)
                                         System.out.println(classify(tests[i]));
                     }
            }
            也許認(rèn)為這個程序會依次打出Set、List和Unkown Collection,但實(shí)際是打印3次Unkown Collection。這是因為classify方法被過載了,選擇使用哪個調(diào)用的決定是在編譯期做出的。

            OK改正的方法是用一個顯式instanceof測試實(shí)例的方法代替classify中的三個過載方法:
            public static String classify(Collection c) {
                     return (c instanceof Set? "Set" :(c instanceof List ? "List" : "Unkown Collection"));
            }

            聽著[我警告你]一種安全、保守的策略是永遠(yuǎn)不要導(dǎo)出兩個具有相同數(shù)目參數(shù)的過載方法。

        • - 返回0長度的數(shù)組而不是null
          • Not OK下面的方法比較常見:
            private List CheesesInStock = ...;

            public Cheese[] getCheeses() {
                     if(cheesesInStock.size() == 0)
                               return null;
                     ...
            }
            沒有理由需要對無奶酪可買這種情況做特殊的處理。著需要客戶方提供額外代碼處理null的返回值,例如:
            Cheese[] cheeses = shop.getCheeses();
            if (cheeses != null && Array.asList(shop.getCheeses()).contains(Cheese.STILTON))
                     System.out.println("Jolly good, just the thing.");
            而不是:
            if (Array.asList(shop.getCheeses()).contains(Cheese.STILTON))
                     System.out.println("Jolly good, just the thing.");

            幾乎每次返回null而不是返回0長度數(shù)組的方法時,都需要這種多余地處理。返回null是易出錯的,因為寫代碼的程序員可能會忘記設(shè)計處理返回null的特殊情形的代碼。

            OK正確的做法是:
            private List cheeseInStock = ...;
            private final static Cheese[] NULL_CHEESE_ARRAY = new Cheese[0];

            public Cheese[] getCheeses() {
                   return (Cheese[]) cheesesInStock.toArray(NULL_CHEESE_ARRAY);
            }

      • - 通用編程
        • - 最小化局部變量作用域
          • 通過最小化局部變量的作用域,可以增加代碼的可讀性和可維護(hù)性,減少出錯可能。
          • 最小化局部變量的最有效的方式是在它第一次被使用時聲明。
          • 幾乎每一個局部變量聲明都應(yīng)該包含一個初始化器(initializer)。
          • 最小化局部變量的最后一個技術(shù)是使方法小而集中。
        • 需要確切答案時,不要使用float或double類型
          • float和double類型特別不適合貨幣計算。
          • Not OK假設(shè)手中有$1.03,花掉0.42后還剩多少錢呢?
            System.out.println(1.03 - .42);
            不幸的是,它會打印0.6100000000000001。
          • OK解決這個問題的正確方法是使用BigDecimal、int和long類型進(jìn)行貨幣計算。
          • 使用BigDecimal有兩個缺點(diǎn)。
            • 它不如使用算術(shù)類型方便。
            • 速度慢。
        • - 盡量避免使用串
          • 串是值類型的糟糕的替代物。
          • 串是可枚舉類型的糟糕的替代物。
          • 串是集合類型的糟糕的替代物
          • 串是capabilities(能力表)的糟糕的替代物
        • - 了解串并置的性能
          • 為連接n個串重復(fù)地使用串合并操作符需要n的二次方時間。
          • 為了獲得可接受的性能,可以使用StringBuffer代替String。
        • 通過接口訪問對象
          • 如果存在合適的接口類型,那么參數(shù)、返回值、變量和域應(yīng)該用接口類型聲明。

            應(yīng)該養(yǎng)成下面這樣的程序習(xí)慣:
            //Good - uses interface as type
            List subscribers = new Vector();

            而不要這樣做:
            //Bad - uses class as type!
            Vector subscribers = new Vector();

            如果養(yǎng)成了使用接口作為類型的習(xí)慣,程序就會有更好的擴(kuò)展性。當(dāng)希望轉(zhuǎn)換實(shí)現(xiàn)時,需要做的全部工作就是改變構(gòu)造函數(shù)中類的名字。

        • 謹(jǐn)慎地做優(yōu)化
          • 人們通常都把計算機(jī)的罪歸咎于效率問題(甚至是不必要的獲得的效率),而不去懷疑任何其他的原因——甚至包括盲目地做傻事?!猈illiam A.Wulf
          • 不要計較微小效率的得失,在97%的情況下,不成熟的優(yōu)化是一切罪惡的根源。 ——Donald E.Knuth
          • 做優(yōu)化時,要遵循兩條原則:
            原則1  不要做優(yōu)化
            原則2(僅對專家)  還是不要做優(yōu)化——也可以這么說:在絕對清楚的、未經(jīng)優(yōu)化的方案之前,不要做優(yōu)化。 
            ——M.A.Jackson

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多