亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Java > 正文

Effective Java讀書筆記六:方法

2019-11-11 04:06:12
字體:
來源:轉載
供稿:網友

第38條:檢查參數的有效性

絕大多數方法和構造器對于傳遞給它們的參數值都會有些限制。比如,索引值必須大于等于0,且不能超過其最大值,對象不能為null等。這樣就可以在導致錯誤的源頭將錯誤捕獲,從而避免了該錯誤被延續到今后的某一時刻再被引發,這樣就是加大了錯誤追查的難度。就如同編譯期能夠報出的錯誤總比在運行時才發現要更好一些。事實上,我們不僅僅需要在函數的內部開始出進行這些通用的參數有效性檢查,還需要在函數的文檔中給予明確的說明,如在參數非法的情況下,會拋出那些異常,或導致函數返回哪些錯誤值等,見如下代碼示例:

/** * Returns a BigInteger whose value is(this mod m). This method * differs from the remainder method in that it always returns a * non-negative BigInteger. * @param m the modulus, which must be positive. * @return this mod m. * @throws ArithmeticException if m is less than or equal to 0.*/ public BigInteger mod(BigInteger m) { if (m.signum() <= 0) throw new ArithmeticException("Modulus <= 0: " + m); ... //Do the computation. }1234567891011121312345678910111213

是不是我們為所有的方法均需要做出這樣的有效性檢查呢?對于未被導出的方法,如包方法等,你可以控制這個方法將在哪些情況下被調用,因此這時可以使用斷言來幫助進行參數的有效性檢查,如:

PRivate static void sort(long a[],int offset,int length) { assert(a != null); assert(offset >= 0 && offset <= a.length); assert(length >= 0 && length <= a.length - offset); ... //Do the computation }123456123456

和通用的檢查方式不同,斷言在其條件為真時,無論外部包得客戶端如何使用它。斷言都將拋出AssertionError。它們之間的另一個差異在于如果斷言沒有起到作用,即-ea命令行參數沒有傳遞給java解釋器,斷言將不會有任何開銷,這樣我們就可以在調試期間加入該命令行參數,在發布時去掉該命令行選項,而我們的代碼則不需要任何改動。

需要強調的是,對于有些函數的參數,其在當前函數內并不使用,而是留給該類其他函數內部使用的,比較明顯的就是類的構造函數,構造函數中的很多參數都不一樣用于構造器內,只是在構造的時候進行有些賦值操作,而這些參數的真正使用者是該類的其他函數,對于這種情況,我們就更需要在構造的時候進行參數的有效性檢查,否則一旦將該問題釋放到域函數的時候,再追查該問題的根源,將不得不付出更大的代價和更多的調試時間。

對該條目的說法確實存在著一種例外情況,在有些情況下有效性檢查工作的開銷是非常大的,或者根本不切實際,因為這些檢查已經隱含在計算過程中完成了,如Collections.sort(List),容器中對象的所有比較操作均在該函數執行時完成,一旦比較操作失敗將會拋出ClassCastException異常。因此對于sort來講,如果我們提前做出有效性檢查將是毫無意義的。

第39條:必要時進行保護性拷貝

如果你的對象沒有做很好的隔離,那么對于調用者而言,則有機會破壞該對象的內部約束條件,因此我們需要保護性的設計程序。該破壞行為一般由兩種情況引起,首先就是惡意的破壞,再有就是調用者無意識的誤用,這兩種條件下均有可能給你的類帶來一定的破壞性,見如下代碼:

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

從表面上看,該類的實現確實對約束性的條件進行了驗證,然而由于Date類本身是可變了,因此很容易違反這個約束,見如下代碼:

public void testPeriod() {         Date start = new Date();         Date end = new Date();         Period p = new Period(start,end);         end.setYear(78);  //該修改將直接影響Period內部的end對象。     }123456123456

為了避免這樣的攻擊,我們需要對Period的構造函數進行相應的修改,即對每個可變參數進行保護性拷貝。

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

需要說明的是,保護性拷貝是在堅持參數有效性之前進行的,并且有效性檢查是針對拷貝之后的對象,而不是針對原始對象的。這主要是為了避免在this.start = new Date(start.getTime())到if (start.compareTo(end) > 0)這個時間窗口內,參數start和end可能會被其他線程修改。

現在構造函數已經安全了,后面我們需要用同樣的方式繼續修改另外兩個對象訪問函數。

    public Date start() {         return new Date(start.getTime());     }    public Date end() {         return new Date(end.getTime());     }123456123456

經過這一番修改之后,Period成為了不可變類,其內部的“周期的起始時間不能落后于結束時間”約束條件也不會再被破壞。

參數的保護性拷貝并不僅僅針對不可變類。每當編寫方法或者構造器時,如果它要允許客戶提供的對象進入到內部數據結構中,則有必要考慮一下,客戶提供的對象進入到內部數據結構中,則有必要考慮一下,客戶提供的對象是否有可能是可變的。如果是,就要考慮你的類是否能夠容忍對象進入數據結構之后發生變化。如果答案是否定的,就必須對該對象進行保護性拷貝,并且讓拷貝之后的對象而不是原始對象進入到數據結構中。

例如,如果你正在考慮使用有客戶提供的對象引用作為內部Set實例的元素,或者作為內部Map實例的鍵(Key),就應該意識到,如果這個對象在插入之后再被修改,Set或者Map的約束條件就會遭到破壞。

第40條:謹慎設計方法簽名

謹慎地選擇方法的名稱避免過長的參數列表,目標是四個參數或者更少,如果多于四個了就該考慮重構這個方法了(把方法分解多個小方法、創建輔助類、從對象構建到方法調用都采用Builder模式)。對于參數類型、要優先使用接口而不是類。如果使用的是類而不是接口,則限制了客戶端只能傳入特定的實現,如果碰巧輸入的數據是以其他的形式存在,就會導致不必要的、可能非常昂貴的拷貝操作。對于boolean參數,優先使用兩個元素的枚舉類型。

第41條:慎用重載

下面的例子根據一個集合是Set、List還是其他的集合類型,來對它進行分類:

  public class CollectionClassfier {             public static String classify(Set<?> s) {                 return "Set";             }             public static String classify(List<?> l) {                 return "List";             }             public static String classify(Collection<?> c) {                 return "Unknown collection";             }             public static void main(String[] args) {                 Collection<?>[] collections = {new HashSet<String>(), new ArrayList<BigInteger>(), new HashMap<String,String>().values()};                 for (Collection<?> c : collections)                     System.out.println(classify(c));             }         } 1234567891011121314151612345678910111213141516

這里你可能會期望程序打印出Set、List、Unknown Collection,然而實際上卻不是這樣,輸出的結果是3 個”Unknown Collection”。 因為classify方法被重載了,需要調用哪個函數是在編譯期決定的,for中的三次迭代參數的編譯類型是相同的:

Collection<?>11

對于重載方法的選擇是靜態的,而對于被覆蓋的方法的選擇則是動態的。選擇被覆蓋的方法的正確版本是在運行時進行的,選擇的依據是被調用的方法所在對象的運行時類型。這里重新說明一下,當一個子類包含的方法聲明與其祖先類中的方法聲明具有同樣的的簽名時,方法就被覆蓋了。如果實例方法在子類中被覆蓋了,并且這個方法是在該子類的實例上被調用的,那么子類中的覆蓋方法將會執行,而不管該子類實例的編譯時類型到底是什么。

        class Wine{             String name() {return "wine"; }         }         class SparklingWine extends Wine{             @Override String name(){return "sparkling wine"; }         }         class Champagne extends Wine{             @Override String name(){return "Champagne"; }         }         public class Overriding{             public static void main(String[] args){                 Wine[] = {                new Wine(),                 new SparklingWine(),                 new Champagne() };             }             for(Wine wine : wines){                 System.out.println(wine.name());             }         } 12345678910111213141516171819201234567891011121314151617181920

正如你所預期的那樣,這個程序打印出“wine, sparkling wine, champagne”,當調用被覆蓋的方法時,對象的編譯時類型不會影響到哪個方法將被執行。最為具體的那個覆蓋版本總是會得到執行。

對于開始的集合輸出類的最佳修正方案是,用單個方法來替換這三個重載的classity方法,如下:

public static String classify(Collection<?> c) {             return c instanceof Set ? "Set" : c instanceof List ? "List" : "Unknown Collection";         } 123123

因此,應該避免胡亂地使用重載機制。 一、安全而保守的策略是,永遠不要導出兩個具有相同參數數目的重載方法。比如兩個重載函數均有一個參數,其中一個是整型,另一個是Collection<?>,對于這種情況,int 和Collection<?>之間沒有任何關聯,也無法在兩者之間做任何的類型轉換,否則將會拋出ClassCastException 的異常,因此對于這種函數重載,我們是可以準確確定的。反之,如果兩個參數分別是int 和short,他們之間的差異就不是這么明顯。 二、如果方法使用可變參數,保守的策略是根本不要重載它。 三、對于構造器,你沒有選擇使用不同名稱的機會,一個類的多個構造器總是重載的,但是構造器也不可能被覆蓋。 四、在Java 1.5 之后,需要對自動裝箱機制保持警惕。 演示如下:

 public class SetList {             public static void main(String[] args) {                 Set<Integer> s = new TreeSet<Integer>();                 List<Integer> l = new ArrayList<Integer>();                 for (int i = -3; i < 3; ++i) {                     s.add(i);                     l.add(i);                 }                 for (int i = 0; i < 3; ++i) {                     s.remove(i);                     l.remove(i);                 }                 System.out.println(s + " " + l);             }         } 123456789101112131415123456789101112131415

在執行該段代碼前,我們期望的結果是Set 和List 集合中大于等于的元素均被移除出容器,然而在執行后卻發現事實并非如此,其結果為:[-3,-2,-1] [-2,0,2]。這個結果和我們的期望還是有很大差異的,為什么Set 中的元素是正確的,而List 則不是,是什么導致了這一結果的發生呢?

下面給出具體的解釋:

s.remove(i)調用的是Set 中的remove(E),這里的E 表示Integer,Java 的編譯器會將i 自動裝箱到Integer 中,因此我們得到了想要的結果。

l.remove(i)實際調用的是List 中的remove(int index)重載方法,而該方法的行為是刪除集合中指定索引的元素。這里分別對應第0 個,第1 個和第2 個。

為了解決這個問題,我們需要讓List 明確的知道,我們需要調用的是remove(E)重載函數,而不是其他的,這樣我們就需要對原有代碼進行如下的修改:

public class SetList {             public static void main(String[] args) {                 Set<Integer> s = new TreeSet<Integer>();                 List<Integer> l = new ArrayList<Integer>();                 for (int i = -3; i < 3; ++i) {                     s.add(i);                     l.add(i);                 }                 for (int i = 0; i < 3; ++i) {                     s.remove(i);                     l.remove((Integer)i); //or remove(Integer.valueOf(i));                 }                 System.out.println(s + " " + l);             }         } 123456789101112131415123456789101112131415

總結,對于多個具有相同參數數目的方法來說,應該盡量避免重載方法。我們應當保證:當傳遞同樣的參數時,所有重載方法的行為必須一致。

第42條:慎用可變參數

可變數組機制是通過先創建一個數組,數組的大小為在調用位置所傳遞的參數數量,然后將參數值傳到數組中,最后將數組傳遞給方法。

有的時候在重視性能的情況下,使用可變參數機制要特別小心??勺儏捣椒ǖ拿看握{用都會導致進行一次數組分配和初始化。如果確定確實無法承受這一成本,但又需要可變參數的靈活性,還有一種模式可以彌補這一不足。假設確定對某個方法95%的調用會有3個或者更少的參數,就聲明該方法的5個重載,每個重載方法帶有0個至3個普通參數,當參數的數目超過3個時,就使用一個可變參數方法:

public void foo() {}public void foo(int a1) {}public void foo(int a1,int a2) {}public void foo(int a1,int a2,int a3) {}public void foo(int a1,int a2,int a3,int...rest) {}1234512345

所有調用中只有5%參數數量超過3個的調用需要創建數組。就像大多數的性能優化一樣,這種方法通常不恰當,但是一旦真正需要它時,還是非常有用處的。

在定義參數數目不定的方法時,可變參數方法是一種很方便的方式,但是它們不應該過度濫用。如果使用不當,會產生混亂的結果。

第43條:返回零長度的數組或者集合,而不是null

有時候會有人認為:null返回值比零長度數據更好,因為它避免了分配數組所需要的開銷。 這種觀點是站不住腳的,原因有兩點。

在這個級別上擔心性能問題是不明智的,除非分析表明這個方法正是造成性能問題的真正源頭。對于不返回任何元素的調用,每次都返回同一個零長度數組是有可能的,因為零長度數組是不可變的,而不可變對象有可能被自由地共享。
private static final Cheese[] EMPTY_CHEESE_ARRAY= new Cheese[0];11

相比于數組,集合亦是如此。 在Collections中有專門針對List,Set,Map的空的實現。如:

Collections.emptyList()Collections.emptySet();Collections.emptyMap();123123

第44條:為所有導出的API元素編寫文檔注釋

《Effective Java中文版 第2版》PDF版下載: http://download.csdn.net/detail/xunzaosiyecao/9745699

作者:jiankunking 出處:http://blog.csdn.net/jiankunking

from: http://blog.csdn.net/jiankunking/article/details/54879754


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品美女久久久久av超清| 欧美成人免费视频| 国产精品99久久久久久www| 亚洲国模精品私拍| 国产亚洲精品91在线| 日韩激情第一页| 欧美日韩一区二区在线| 精品国产视频在线| 国产精品电影久久久久电影网| 久久久久九九九九| 最近免费中文字幕视频2019| 亚洲免费影视第一页| 亚洲网站在线播放| 久久久成人精品视频| 精品一区二区三区四区| 国产成人精品在线观看| 日韩在线中文视频| 亚洲精品免费在线视频| 国产噜噜噜噜久久久久久久久| 日韩有码在线电影| 日韩网站免费观看| 日韩高清有码在线| 欧美国产日本在线| 国产视频精品一区二区三区| 国自产精品手机在线观看视频| 欧美精品久久久久久久免费观看| 国产在线999| 国产91色在线|| 亚洲欧洲国产一区| 综合网中文字幕| 国产视频一区在线| 久久精品色欧美aⅴ一区二区| 亚洲视频视频在线| 欧美午夜女人视频在线| 久久久精品电影| 性欧美视频videos6一9| 久久久久久久久久久久久久久久久久av| 中文在线不卡视频| 亚洲最大av网| 欧美日韩国产一区中文午夜| 亚洲免费电影在线观看| 国产欧美精品一区二区三区-老狼| 国产精品久久中文| 久久国产精品电影| 久热精品视频在线观看| 亚洲毛片在线免费观看| 色综久久综合桃花网| 免费91麻豆精品国产自产在线观看| 日韩激情片免费| 久久精品国产2020观看福利| 国产精品7m视频| 成人国产精品一区二区| 亚洲第五色综合网| 日本国产高清不卡| 欧美裸体xxxxx| 日韩在线观看视频免费| 日韩高清有码在线| 中文字幕亚洲一区二区三区| 亚洲第一天堂无码专区| 欧美精品一区二区免费| 一区二区中文字幕| 97香蕉久久超级碰碰高清版| 色婷婷综合成人av| 亚洲精品国产精品国产自| 欧美国产日产韩国视频| 欧美一区二区三区四区在线| 国产精品一区二区性色av| 久久综合久中文字幕青草| 日韩国产欧美精品一区二区三区| 国产精品日韩在线播放| 久久99视频精品| 97成人超碰免| 亚洲在线www| www.久久色.com| 青青草精品毛片| 亚洲欧美一区二区三区四区| 欧美激情在线观看| www国产精品com| 欧美激情久久久久久| 一区二区成人精品| 欧美成aaa人片免费看| 亚洲日本aⅴ片在线观看香蕉| 26uuu另类亚洲欧美日本老年| 欧美另类99xxxxx| 成人久久一区二区| 久久亚洲精品网站| 日韩精品久久久久久福利| 欧洲美女免费图片一区| 欧美在线观看日本一区| 中文字幕国产精品久久| 欧美性少妇18aaaa视频| 国产免费一区二区三区香蕉精| 日韩一区二区久久久| 亚洲精品mp4| 久久精品这里热有精品| 日韩欧美成人网| 亚洲欧洲第一视频| 久久香蕉频线观| 5252色成人免费视频| 青青草原成人在线视频| 国产专区精品视频| 亚洲欧美国产一区二区三区| 久久这里有精品| 国产精品久久婷婷六月丁香| 久久久91精品国产一区不卡| 亚洲加勒比久久88色综合| 欧美性受xxxx黑人猛交| 欧美成年人视频网站欧美| 久久久久久国产精品三级玉女聊斋| 久久精品一本久久99精品| 日韩精品在线影院| 国产欧美日韩综合精品| 欧美激情va永久在线播放| 国产在线999| 亚洲一级一级97网| 亚洲国产精品中文| 欧美性受xxxx黑人猛交| 国产精品久久久久久久天堂| 日韩av在线播放资源| 国产一区二区三区直播精品电影| 亚洲天堂成人在线| 亚洲网址你懂得| 亚洲xxxxx| 亚洲第一二三四五区| 中文字幕久热精品在线视频| 欧美日韩国产丝袜美女| 亚洲精品小视频| 亚洲福利视频二区| 亚洲精品视频中文字幕| 日韩av电影中文字幕| 成人444kkkk在线观看| 国产成人精品免费久久久久| 国产精品久久久久久av下载红粉| 久久夜色精品国产欧美乱| 一本色道久久88综合日韩精品| 成人午夜两性视频| 亚洲2020天天堂在线观看| 正在播放欧美一区| 一区二区成人av| 国产精品免费久久久久影院| 亚洲人成网站777色婷婷| 538国产精品一区二区在线| 亚洲国产成人在线播放| 日韩在线视频免费观看高清中文| 亚洲色图欧美制服丝袜另类第一页| 在线看日韩欧美| 国产成人精品视频在线观看| 日韩美女视频中文字幕| 精品久久久久久亚洲国产300| 国产精品久久久久av| 九九精品视频在线| 欧美精品18videosex性欧美| 日韩激情av在线免费观看| 精品久久久一区| 国产一区二区精品丝袜| 久久国产精品久久久久| 国产91ⅴ在线精品免费观看| 久久精品中文字幕免费mv| 欧美日韩亚洲一区二区| 欧美久久久精品| 国产手机视频精品| 亚洲国产成人在线视频| 日韩欧美高清视频| 国产精品第三页|