I want to fly higher
          programming Explorer
          posts - 114,comments - 263,trackbacks - 0
          第四條:通過私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力。
           
            1.有時(shí)候,你可能需要編寫至包含靜態(tài)方法和靜態(tài)域的類。這些類的名聲很不好,因?yàn)橛行┤嗽诿嫦驅(qū)ο蟮恼Z言中濫用這樣的類來編寫過程化的程序。
            2.盡管如此,他們也確實(shí)有它們特有的好處:
                 1.利用這種類,以java.lang.Math或者java.util.Arrays的方式,把基本類型的值或者數(shù)組類型上的相關(guān)方法組織起來.
                 2.我們也可以通過java.util.Collections的方式,把實(shí)現(xiàn)特定接口的對(duì)象上的靜態(tài)方法包括工廠方法組織起來。
                 3.利用這種類可以把final類上的方法組織起來,以取代擴(kuò)展該類的做法。
            3.這樣的工具類Unility class不希望被實(shí)例化,實(shí)例對(duì)它沒有任何意義。然而在缺少顯示構(gòu)造器的情況下,編譯器會(huì)自動(dòng)提供一個(gè)公有的,無參的缺省構(gòu)造器default constructor.對(duì)于用戶而言,這個(gè)構(gòu)造器與其他的構(gòu)造器沒有任何區(qū)別。在已發(fā)行的API中常常可以看到一些被無意識(shí)地實(shí)例化的類。
            4.企圖通過將類做成抽象類來強(qiáng)制該類不可被實(shí)例化,這是行不通的。該類可以被子類化,并且該子類也可被實(shí)例化。這樣做甚至?xí)`導(dǎo)用戶,以為這種類是專門為了繼承而設(shè)計(jì)的。然而有一些簡(jiǎn)單的習(xí)慣用法可以確保類不可被實(shí)例化。
            由于只有當(dāng)類不包含顯示的的構(gòu)造器時(shí),編譯器才會(huì)生成缺省的構(gòu)造器,因?yàn)槲覀冎灰屵@個(gè)類包含私有的構(gòu)造器,它就不能被實(shí)例化了:
            public class UtilityClass
            {
             private UtilityClass()
             {
              throw new AssertionError();
             }
             
             //...others
            }
                由于顯示的構(gòu)造器是私有的,所以不可以再該類的外部訪問它。AssertionError不是必須的。但是它可以避免不小心在類的內(nèi)部調(diào)用構(gòu)造器。它保證該類在任何情況下都不會(huì)被實(shí)例化。
                注:1.但是這種用法有點(diǎn)違背直覺,好像構(gòu)造器就是專門設(shè)計(jì)成不能調(diào)用一樣。因此明智的做法就是在代碼中增加一條注釋,如:
                 //Supress default constructor for noninstantiability.
                 2.AssertionError:拋出該異常指示某個(gè)斷言失敗
            5.這種慣用法的副作用:它使得一個(gè)類不能被子類化。所有的構(gòu)造器都必須顯示或隱式的調(diào)用超類構(gòu)造器。在這種情形下子類就沒有可訪問的超類構(gòu)造器可調(diào)用了。
            
           第5條:避免創(chuàng)建不必要的對(duì)象
           
            1.一般說來,最好能重用對(duì)象而不是在每次需要的時(shí)候就創(chuàng)建一個(gè)相同功能的新對(duì)象。重用方式即快速,又流行。如果對(duì)象是不可變的immutable,它就始終可以被重用。
            極端反面的例子:
                String s = new String("landon");//Don't do this!
                該語句每次執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)新的String實(shí)例,但是這些創(chuàng)建對(duì)象的動(dòng)作全都是不必要的。傳遞給String構(gòu)造器的參數(shù)("landon")本身就是一個(gè)String實(shí)例,功能方面等同于構(gòu)造器創(chuàng)建的所有對(duì)象。
                如果上述用法是在一個(gè)循環(huán)中,或者是在一個(gè)被頻繁調(diào)用的方法中,就會(huì)創(chuàng)建愛你出成千上萬不必要的String實(shí)例。改進(jìn):String s = "landon".該版本只用了一個(gè)String實(shí)例,而不是每次執(zhí)行的時(shí)候都創(chuàng)建一個(gè)新的實(shí)例。而且其可以保證,對(duì)于所有在同一虛擬機(jī)中運(yùn)行的代碼,只要其包含相同的字符串字面常量,該對(duì)象就會(huì)被常用(注:String常量池,@link String#intern)。
                對(duì)于同時(shí)提供了靜態(tài)工廠方法和構(gòu)造器的不可變類,通常可以使用靜態(tài)工廠方法而不是構(gòu)造器,以避免創(chuàng)建不必要的對(duì)象。如Boolean.valueOf(String)幾乎總是優(yōu)先于構(gòu)造器Boolean(String).構(gòu)造器在每次調(diào)用的時(shí)候都會(huì)創(chuàng)建一個(gè)新的對(duì)象,而靜態(tài)工廠方法則從來不要求這樣做,實(shí)際上也不會(huì)這樣做.
            2.除了重用不可變的對(duì)象外,也可以重用那些已知不會(huì)修改的可變對(duì)象。下面這個(gè)也是可以較常見的反面例子,其中涉及可變的Date對(duì)象,它們的值一旦計(jì)算出來之后就不再變化。
            Person#isBabyBoomer(),檢查這個(gè)人是否是生育高峰期出生的小孩,即1946至1964年間。
            public boolean isBabyBoomer()
            {
              Calendar gmtCalendar = Calendar
                .getInstance(TimeZone.getTimeZone("GMT"));
            
              // 1946
              gmtCalendar.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
              Date boomStart = gmtCalendar.getTime();
            
              gmtCalendar.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
              Date boomEnd = gmtCalendar.getTime();
            
              return birthDate.compareTo(boomStart) >= 0
                && birthDate.compareTo(boomEnd) <= 0;
            }
            該方法每次調(diào)用的時(shí)候都會(huì)創(chuàng)建一個(gè)Calendar,一個(gè)TimeZone和兩個(gè)Date實(shí)例,這都是不必要的;
            改進(jìn):
            static
            {
             Calendar gmtCalendar = Calendar
               .getInstance(TimeZone.getTimeZone("GMT"));
           
             // 1946
             gmtCalendar.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
             BOOM_START = gmtCalendar.getTime();
           
             gmtCalendar.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
             BOOM_END = gmtCalendar.getTime();
            }
                <p>改進(jìn)后的方法只在初始化的時(shí)候創(chuàng)建Calender,TimeZone和Date實(shí)例一次。如果該方法頻繁調(diào)用,則會(huì)顯著的提高性能。
                <p>除了提高性能之外,代碼的含義也更清晰了,BOOM_START和BOOM_END從局部變量改為final靜態(tài)域,顯然就應(yīng)該作為常量對(duì)待。
                 從而使代碼更易于理解。但是這種優(yōu)化帶來的效果不總是那么明顯,因?yàn)镃alendar實(shí)例的創(chuàng)建代價(jià)特別昂貴。
                 3.如果改進(jìn)的Person類被初始化了,它的isBabyBoomer方法卻永遠(yuǎn)不會(huì)被調(diào)用,那就沒有必要初始化BOOM_START和BOOM_END域。通過延遲初始化lazy initializing,即把對(duì)這些域的初始化延遲到isBabyBoomer方法第一次被調(diào)用的時(shí)候進(jìn)行,則有可能消除這些不必要的初始化工作。但是不建議這樣做。正如延遲初始化中常見的情況一樣,這樣做會(huì)使方法的實(shí)現(xiàn)更加復(fù)雜,從而無法將性能顯著提高到超過已經(jīng)達(dá)到的水平。
             4.前面的例子中,討論到的對(duì)象均是能夠被重用的,因?yàn)樗鼈冊(cè)诒怀跏蓟蟛粫?huì)再改變。考慮適配器adapter情形,有時(shí)也叫做視圖view。適配器是指這樣一個(gè)對(duì)象,它把功能委托一個(gè)后備對(duì)象backing object,從而為后備對(duì)象提供一個(gè)可以替代的接口。由于適配器除了后備對(duì)象之外,沒有其他的狀態(tài)信息,所以針對(duì)某個(gè)給定對(duì)象的特定適配器而言,它不需要?jiǎng)?chuàng)建多個(gè)適配器實(shí)例。
              如:Map接口的keySet方法返回該Map對(duì)象的Set視圖,其中包含該Map中所有的key.粗看起來,好友每次調(diào)用keySet都應(yīng)該創(chuàng)建一個(gè)新的Set實(shí)例。但是對(duì)于一個(gè)給定的Map對(duì)象,實(shí)際上每次調(diào)用keySet都返回同樣的Set實(shí)例。雖然被返回的Set實(shí)例一般是可改變的,但是所有返回的對(duì)象在功能上是等同的。當(dāng)其中一個(gè)返回對(duì)象發(fā)生變化的時(shí)候,所有其他的返回對(duì)象也要發(fā)生變化。因?yàn)樗鼈兪怯赏粋€(gè)Map實(shí)例支撐的。 雖然創(chuàng)建keySet視圖對(duì)象的多個(gè)實(shí)例并無害處,卻也是沒有必要的。
             5.在Java1.5發(fā)行版本中,有一種創(chuàng)建多余對(duì)象的新方法,稱作自動(dòng)裝箱autoboxing。它允許程序員將基本類型和裝箱類型Boxed primitive Type混用,暗需要自動(dòng)裝箱和拆箱。自動(dòng)裝箱使得基本類型和裝箱基本類型的差別變的模糊起來,但是并沒有完全消除。他們?cè)谡Z義上還有這微妙的區(qū)別,在性能上也有著比較明顯的差別。
             下面這段程序,計(jì)算所有int正值的總和,用long,因?yàn)閕nt不夠大:
             
              // 這里用的是Long,程序運(yùn)行會(huì)非常慢,因?yàn)槌绦虼蠹s構(gòu)造了2|31個(gè)多余的Long實(shí)例
             //Long sum = 0L;
             long sum = 0L;
             
             for(long i = 0;i < Integer.MAX_VALUE;i++)
             {
              sum += i;
             }
            將sum的聲明從Long變?yōu)閘ong,程序運(yùn)行時(shí)間會(huì)減慢很多。結(jié)論很明顯:要優(yōu)先使用基本類型而不是裝箱基本類型,要當(dāng)心無意識(shí)的自動(dòng)裝箱。
            6.不要錯(cuò)誤的認(rèn)為本條目所介紹的內(nèi)容暗示著創(chuàng)建對(duì)象的代建非常昂貴,我們 應(yīng)該盡可能避免的創(chuàng)建對(duì)象。相反,由于小對(duì)象的構(gòu)造器只做少量的顯示工作,所以小對(duì)象的創(chuàng)建和回收動(dòng)作是非常廉價(jià)的,特別是在現(xiàn)代的jvm實(shí)現(xiàn)更是如此。通過創(chuàng)建附加的對(duì)象,提升程序的清晰性,簡(jiǎn)潔行和功能性,這通常是件好事。
            7.反之通過維護(hù)自己的對(duì)象池object pool來避免創(chuàng)建對(duì)象并不是一種好的做法,除非池中的對(duì)象是非常重量級(jí)的。真正正確使用對(duì)象池的典型對(duì)象示例就是數(shù)據(jù)庫連接池。建立數(shù)據(jù)庫連接的代碼是非常昂貴的,因此重用這些對(duì)象非常有意義。而且數(shù)據(jù)庫的許可可能限制你只能使用一定數(shù)量的連接。但是一般而言,維護(hù)自己的對(duì)象池必定會(huì)把代碼弄的很亂,同時(shí)增加內(nèi)存使用footprint,并且還會(huì)損害性能。現(xiàn)代的JVM實(shí)現(xiàn)具有高度優(yōu)化的垃圾回收器,其性能很容易就會(huì)超出輕量級(jí)對(duì)象池的性能。
            8.與本條目對(duì)應(yīng)的是保護(hù)性拷貝defensive copying的內(nèi)容。本題目提及:當(dāng)你應(yīng)該重用現(xiàn)有對(duì)象的時(shí)候,請(qǐng)不要?jiǎng)?chuàng)建新對(duì)象。而39條則說,你應(yīng)該創(chuàng)建新對(duì)象的時(shí)候,請(qǐng)不要重用現(xiàn)有對(duì)象。注意,在提倡保護(hù)性拷貝的時(shí)候,因重用對(duì)象而付出的代碼要遠(yuǎn)遠(yuǎn)大于因創(chuàng)建重復(fù)對(duì)象而付出的代價(jià)。必要時(shí)如果沒能實(shí)施保護(hù)性考慮,將會(huì)導(dǎo)致潛在的錯(cuò)誤和安全漏洞,而不必要地創(chuàng)建對(duì)象則只會(huì)影響程序的風(fēng)格和性能。

          部分源碼:
          package com.book.chap2.privateConstructor;

          /**
          *
          *工具類
          *<p>因?yàn)槭枪ぞ哳悾圆幌M粚?shí)例化,而且實(shí)例對(duì)其沒有任何意義。</p>
          *<p>采用私有構(gòu)造器來防止實(shí)例化(副作用,不可被子類化)</p>
          *<p>可在私有構(gòu)造器中拋出異常避免不小心在類的內(nèi)部調(diào)用構(gòu)造器</p>
          *
          *@author landon
          *@since 1.6.0_35
          *@version 1.0.0 2013-1-10
          *
          */


          public class UtilityClass
          {
             
          //Supress default constructor for noninstantiability
              private UtilityClass()
             
          {
                 
          throw new AssertionError();
              }

          }


          package com.book.chap2.avoidCreateUnnecessaryObject;

          /**
          *
          *自動(dòng)裝箱問題
          *
          *@author landon
          *@since 1.6.0_35
          *@version 1.0.0 2013-1-24
          *
          */


          public class AutoBoxingProblem
          {
             
          public static void main(Stringargs)
             
          {
                 
          // 這里用的是Long,程序運(yùn)行會(huì)非常慢,因?yàn)槌绦虼蠹s構(gòu)造了2|31個(gè)多余的Long實(shí)例
                 
          //Long sum = 0L;
                  long sum = 0L;
                 
                 
          for(long i = 0;i < Integer.MAX_VALUE;i++)
                 
          {
                      sum += i;
                  }

                 
                  System.out.println(sum);
              }

          }


          package com.book.chap2.avoidCreateUnnecessaryObject;

          import java.util.Calendar;
          import java.util.Date;
          import java.util.TimeZone;

          /**
          *
          * 檢查一個(gè)人是否出生于1946-1964的生育期高峰
          *
          * @author landon
          * @since 1.6.0_35
          * @version 1.0.0 2013-1-23
          *
          */


          public class Person
          {
             
          private final Date birthDate;

             
          public Person(Date birth)
             
          {
                  birthDate = birth;
              }


             
          /**
               *
               * 是否出生在高峰期
               * <p>
               * 該方法每次調(diào)用的時(shí)候都會(huì)創(chuàng)建一個(gè)Calendar,一個(gè)TimeZone和兩個(gè)Date實(shí)例,這都是不必要的
               *
               * @return
              
          */

             
          public boolean isBabyBoomer()
             
          {
                  Calendar gmtCalendar = Calendar
                          .getInstance(TimeZone.getTimeZone("GMT"));

                 
          // 1946
                  gmtCalendar.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
                  Date boomStart = gmtCalendar.getTime();

                  gmtCalendar.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
                  Date boomEnd = gmtCalendar.getTime();

                 
          return birthDate.compareTo(boomStart) >= 0
                         
          && birthDate.compareTo(boomEnd) <= 0;
              }


             
          // 對(duì)于以上情況,采用靜態(tài)初始化器,避免上面這種效率低下的情況
              private static final Date BOOM_START;
             
          private static final Date BOOM_END;

             
          // 靜態(tài)初始化器
              static
             
          {
                  Calendar gmtCalendar = Calendar
                          .getInstance(TimeZone.getTimeZone("GMT"));

                 
          // 1946
                  gmtCalendar.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
                  BOOM_START = gmtCalendar.getTime();

                  gmtCalendar.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
                  BOOM_END = gmtCalendar.getTime();
              }


             
          /**
               *
               * 第二種方法判斷是否生在高峰期
               * <p>改進(jìn)后的方法只在初始化的時(shí)候創(chuàng)建Calender,TimeZone和Date實(shí)例一次。如果該方法頻繁調(diào)用,則會(huì)顯著的提高性能。
               * <p>除了提高性能之外,代碼的含義也更清晰了,BOOM_START和BOOM_END從局部變量改為final靜態(tài)域,顯然就應(yīng)該作為常     *
               *
          量對(duì)待。
               *         從而使代碼更易于理解。
          @return
              
          */

             
          public boolean isBabyBoomer2()
             
          {
                 
          return birthDate.compareTo(BOOM_START) >= 0
                         
          && birthDate.compareTo(BOOM_END) <= 0;
              }

          }

          posted on 2013-03-15 16:10 landon 閱讀(1829) 評(píng)論(0)  編輯  收藏 所屬分類: ProgramBook
          主站蜘蛛池模板: 乐陵市| 资兴市| 宁乡县| 车险| 布拖县| 武川县| 太康县| 巍山| 寿阳县| 宣城市| 焦作市| 贺兰县| 册亨县| 台江县| 汶川县| 东阿县| 郓城县| 临汾市| 贵德县| 泾源县| 都匀市| 宁国市| 镇安县| 福州市| 晴隆县| 山阴县| 萍乡市| 民乐县| 克东县| 马公市| 兴国县| 上林县| 隆化县| 驻马店市| 阿图什市| 五台县| 章丘市| 聊城市| 固镇县| 襄汾县| 威宁|