日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

Effective Java (方法)

系統(tǒng) 2321 0

三十八、檢查參數(shù)的有效性:

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

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

?? ??? 是不是我們?yōu)樗械姆椒ň枰龀鲞@樣的有效性檢查呢?對(duì)于未被導(dǎo)出的方法,如包方法等,你可以控制這個(gè)方法將在哪些情況下被調(diào)用,因此這時(shí)可以使用斷言來幫助進(jìn)行參數(shù)的有效性檢查,如:

        
          1
        
        
          private
        
        
          static
        
        
          void
        
         sort(
        
          long
        
         a[],
        
          int
        
         offset,
        
          int
        
         length) {


        
          2
        
        
          assert
        
        (a != 
        
          null
        
        );


        
          3
        
        
          assert
        
        (offset >= 0 && offset <= a.length);

        
          
4
        
        
          assert
        
        (length >= 0 && length <= a.length - offset);

        
          
5
        
                  ... 
        
          //
        
        
          Do the computation
        
        
          6
        
              }
      

?? ??? 和通用的檢查方式不同,斷言在其條件為真時(shí),無(wú)論外部包得客戶端如何使用它。斷言都將拋出AssertionError。它們之間的另一個(gè)差異在于如果斷言沒有起到作用,即-ea命令行參數(shù)沒有傳遞給java解釋器,斷言將不會(huì)有任何開銷,這樣我們就可以在調(diào)試期間加入該命令行參數(shù),在發(fā)布時(shí)去掉該命令行選項(xiàng),而我們的代碼則不需要任何改動(dòng)。
?? ??? 需要強(qiáng)調(diào)的是,對(duì)于有些函數(shù)的參數(shù),其在當(dāng)前函數(shù)內(nèi)并不使用,而是留給該類其他函數(shù)內(nèi)部使用的,比較明顯的就是類的構(gòu)造函數(shù),構(gòu)造函數(shù)中的很多參數(shù)都不一樣用于構(gòu)造器內(nèi),只是在構(gòu)造的時(shí)候進(jìn)行有些賦值操作,而這些參數(shù)的真正使用者是該類的其他函數(shù),對(duì)于這種情況,我們就更需要在構(gòu)造的時(shí)候進(jìn)行參數(shù)的有效性檢查,否則一旦將該問題釋放到域函數(shù)的時(shí)候,再追查該問題的根源,將不得不付出更大的代價(jià)和更多的調(diào)試時(shí)間。
?? ??? 對(duì)該條目的說法確實(shí)存在著一種例外情況,在有些情況下有效性檢查工作的開銷是非常大的,或者根本不切實(shí)際,因?yàn)檫@些檢查已經(jīng)隱含在計(jì)算過程中完成了,如Collections.sort(List),容器中對(duì)象的所有比較操作均在該函數(shù)執(zhí)行時(shí)完成,一旦比較操作失敗將會(huì)拋出ClassCastException異常。因此對(duì)于sort來講,如果我們提前做出有效性檢查將是毫無(wú)意義的。
?? ? ?
三十九、必要時(shí)進(jìn)行保護(hù)性拷貝:

?? ?? 如果你的對(duì)象沒有做很好的隔離,那么對(duì)于調(diào)用者而言,則有機(jī)會(huì)破壞該對(duì)象的內(nèi)部約束條件,因此我們需要保護(hù)性的設(shè)計(jì)程序。該破壞行為一般由兩種情況引起,首先就是惡心的破壞,再有就是調(diào)用者無(wú)意識(shí)的誤用,這兩種條件下均有可能給你的類帶來一定的破壞性,見如下代碼:

1 ???? public final class Period {
2 ???????? private final Date start;
3 ???????? private final Date end;
4 ???????? public Period(Date start,Date end) {
5 ???????????? if (start.compareTo(end) > 0) {
6 ???????????????? throw new IllegalArgumentException(start + "After " + end);
7 ???????????? this .start = start;
8 ???????????? this .end = end;
9 ???????? }
10 ???????? public Date start() {
11 ???????????? return start;
12 ???????? }
13 ???????? public Date end() {
14 ???????????? return end;
15 ???????? }
16 ???? }
?

????? 從表面上看,該類的實(shí)現(xiàn)確實(shí)對(duì)約束性的條件進(jìn)行了驗(yàn)證,然而由于Date類本身是可變了,因此很容易違反這個(gè)約束,見如下代碼:

        
          1
        
        
          public
        
        
          void
        
         testPeriod() {


        
          2
        
                 Date start = 
        
          new
        
         Date();


        
          3
        
                 Date end = 
        
          new
        
         Date();


        
          4
        
                 Period p = 
        
          new
        
         Period(start,end);


        
          5
        
                 end.setYear(78);  
        
          //
        
        
          該修改將直接影響Period內(nèi)部的end對(duì)象。
        
        
          
6
        
             }
      

????? 為了避免這樣的攻擊,我們需要對(duì)Period的構(gòu)造函數(shù)進(jìn)行相應(yīng)的修改,即對(duì)每個(gè)可變參數(shù)進(jìn)行保護(hù)性拷貝。

        
          1
        
        
          public
        
         Period(Date start,Date end) {


        
          2
        
        
          this
        
        .start = 
        
          new
        
         Date(start.getTime());

        
          
3
        
        
          this
        
        .end = 
        
          new
        
         Date(end.getTime());

        
          
4
        
        
          if
        
         (start.compareTo(end) > 0) {


        
          5
        
        
          throw
        
        
          new
        
         IllegalArgumentException(start + "After " + end);


        
          6
        
             }
      

????? 需要說明的是,保護(hù)性拷貝是在堅(jiān)持參數(shù)有效性之前進(jìn)行的,并且有效性檢查是針對(duì)拷貝之后的對(duì)象,而不是針對(duì)原始對(duì)象的。這主要是為了避免在this.start = new Date(start.getTime())到if (start.compareTo(end) > 0)這個(gè)時(shí)間窗口內(nèi),參數(shù)start和end可能會(huì)被其他線程修改。
?? ?? 現(xiàn)在構(gòu)造函數(shù)已經(jīng)安全了,后面我們需要用同樣的方式繼續(xù)修改另外兩個(gè)對(duì)象訪問函數(shù)。

        
          1
        
        
          public
        
         Date start() {


        
          2
        
        
          return
        
        
          new
        
         Date(start.getTime());


        
          3
        
             }


        
          4
        
        
          public
        
         Date end() {


        
          5
        
        
          return
        
        
          new
        
         Date(end.getTime());


        
          6
        
             }
      

????? 經(jīng)過這一番修改之后,Period成為了不可變類,其內(nèi)部的“周期的起始時(shí)間不能落后于結(jié)束時(shí)間”約束條件也不會(huì)再被破壞。
?? ?? 參數(shù)的保護(hù)性拷貝并不僅僅針對(duì)不可變類。每當(dāng)編寫方法或者構(gòu)造器時(shí),如果它要允許客戶提供的對(duì)象進(jìn)入到內(nèi)部數(shù)據(jù)結(jié)構(gòu)中,則有必要考慮一下,客戶提供的對(duì)象進(jìn)入到內(nèi)部數(shù)據(jù)結(jié)構(gòu)中,則有必要考慮一下,客戶提供的對(duì)象是否有可能是可變的。如果是,就要考慮你的類是否能夠容忍對(duì)象進(jìn)入數(shù)據(jù)結(jié)構(gòu)之后發(fā)生變化。如果答案是否定的,就必須對(duì)該對(duì)象進(jìn)行保護(hù)性拷貝,并且讓拷貝之后的對(duì)象而不是原始對(duì)象進(jìn)入到數(shù)據(jù)結(jié)構(gòu)中。例如,如果你正在考慮使用有客戶提供的對(duì)象引用作為內(nèi)部Set實(shí)例的元素,或者作為內(nèi)部Map實(shí)例的鍵(Key),就應(yīng)該意識(shí)到,如果這個(gè)對(duì)象在插入之后再被修改,Set或者M(jìn)ap的約束條件就會(huì)遭到破壞。
?? ?
四十一、謹(jǐn)慎重載:

?? ?? 見下面一個(gè)函數(shù)重載的例子:

1 ???? public class CollectionClassfier {
2 ???????? public static String classify(Set<?> s) {
3 ???????????? return "Set";
4 ???????? }
5 ???????? public static String classify(List<?> l) {
6 ???????????? return "List";
7 ???????? }
8 ???????? public static String classify(Collection<?> c) {
9 ???????????? return "Unknown collection";
10 ???????? }
11 ???????? public static void main(String[] args) {
12 ???????????? Collection<?>[] collections = {
13 ???????????????? new HashSet<String>(),
14 ???????????????? new ArrayList<BigInteger>(),
15 ???????????????? new HashMap<String,String>().values()
16 ???????????? };
17 ???????????? for (Collection<?> c : collections)
18 ???????????????? System.out.println(classify(c));
19 ???????? }
20 ???? }
?

????? 這里你可能會(huì)期望程序打印出
?? ?? //Set
?? ?? //List
?? ?? //Unknown Collection
?? ?? 然而實(shí)際上卻不是這樣,輸出的結(jié)果是3個(gè)"Unknown Collection"。為什么會(huì)是這樣呢?因?yàn)楹瘮?shù)重載后,需要調(diào)用哪個(gè)函數(shù)是在編譯期決定的,這不同于多態(tài)的運(yùn)行時(shí)動(dòng)態(tài)綁定。針對(duì)此種情形,該條目給出了一個(gè)修正的方法,如下:

        
          1
        
        
          public
        
        
          static
        
         String classify(Collection<?> c) {


        
          2
        
        
          return
        
         c 
        
          instanceof
        
         Set ? "Set" : c 
        
          instanceof
        
         List 

        
          3
        
                     ? "List" : "Unknown Collection";


        
          4
        
             }
      

????? 和override不同,重載機(jī)制不會(huì)像override那樣規(guī)范,并且每次都能得到期望的結(jié)果。因此在使用時(shí)需要非常謹(jǐn)慎,否則一旦出了問題,就會(huì)需要更多的時(shí)間去調(diào)試。該條目給出以下幾種盡量不要使用重載的情形:
?? ?? 1.?? ?函數(shù)的參數(shù)中包含可變參數(shù);
? ? ? 2.?? ?當(dāng)函數(shù)參數(shù)數(shù)目相同時(shí),你無(wú)法準(zhǔn)確的確定哪一個(gè)方法該被調(diào)用時(shí);
? ? ? 3.?? ?在Java 1.5 之后,需要對(duì)自動(dòng)裝箱機(jī)制保持警惕。
?? ?? 我們先簡(jiǎn)單說一下第二種情形。比如兩個(gè)重載函數(shù)均有一個(gè)參數(shù),其中一個(gè)是整型,另一個(gè)是Collection<?>,對(duì)于這種情況,int和Collection<?>之間沒有任何關(guān)聯(lián),也無(wú)法在兩者之間做任何的類型轉(zhuǎn)換,否則將會(huì)拋出ClassCastException的異常,因此對(duì)于這種函數(shù)重載,我們是可以準(zhǔn)確確定的。反之,如果兩個(gè)參數(shù)分別是int和short,他們之間的差異就不是這么明顯。
? ? ? 對(duì)于第三種情形,該條目給出了一個(gè)非常典型的用例代碼,如下:

1 ???? public class SetList {
2 ???????? public static void main(String[] args) {
3 ???????????? Set<Integer> s = new TreeSet<Integer>();
4 List<Integer> l = new ArrayList<Integer>();
5 ???????????? for ( int i = -3; i < 3; ++i) {
6 ???????????????? s.add(i);
7 ???????????????? l.add(i);
8 ???????????? }
9 ???????????? for ( int i = 0; i < 3; ++i) {
10 ???????????????? s.remove(i);
11 ???????????????? l.remove(i);
12 ???????????? }
13 ???????????? System.out.println(s + " " + l);
14 ???????? }
15 ???? }
?

?? ?? 在執(zhí)行該段代碼前,我們期望的結(jié)果是Set和List集合中大于等于的元素均被移除出容器,然而在執(zhí)行后卻發(fā)現(xiàn)事實(shí)并非如此,其結(jié)果為:
? ? ? [-3,-2,-1] [-2,0,2]
? ? ? 這個(gè)結(jié)果和我們的期望還是有很大差異的,為什么Set中的元素是正確的,而List則不是,是什么導(dǎo)致了這一結(jié)果的發(fā)生呢?下面給出具體的解釋:
? ? ? 1. s.remove(i)調(diào)用的是Set中的remove(E),這里的E表示Integer,Java的編譯器會(huì)將i自動(dòng)裝箱到Integer中,因此我們得到了想要的結(jié)果。
? ? ? 2. l.remove(i)實(shí)際調(diào)用的是List中的remove(int index)重載方法,而該方法的行為是刪除集合中指定索引的元素。這里分別對(duì)應(yīng)第0個(gè),第1個(gè)和第2個(gè)。
? ? ? 為了解決這個(gè)問題,我們需要讓List明確的知道,我們需要調(diào)用的是remove(E)重載函數(shù),而不是其他的,這樣我們就需要對(duì)原有代碼進(jìn)行如下的修改:

1 ???? public class SetList {
2 ???????? public static void main(String[] args) {
3 ???????????? Set<Integer> s = new TreeSet<Integer>();
4 List<Integer> l = new ArrayList<Integer>();
5 ???????????? for ( int i = -3; i < 3; ++i) {
6 ???????????????? s.add(i);
7 ???????????????? l.add(i);
8 ???????????? }
9 ???????????? for ( int i = 0; i < 3; ++i) {
10 ???????????????? s.remove(i);
11 ???????????????? l.remove((Integer)i); // or remove(Integer.valueOf(i));
12 ???????????? }
13 ???????????? System.out.println(s + " " + l);
14 ???????? }
15 ???? }
?

? ? ? 該條目還介紹了一種實(shí)現(xiàn)函數(shù)重載,同時(shí)又盡可能避免上述錯(cuò)誤發(fā)生的方式。即其中的一個(gè)重載函數(shù),在其內(nèi)部通過一定的轉(zhuǎn)換邏輯轉(zhuǎn)換之后,再通過轉(zhuǎn)換后的參數(shù)類型調(diào)用其他的重載函數(shù),從而確保即便使用者在使用過程中出現(xiàn)重載誤用的情況,也因兩者可以得到相同的結(jié)果而規(guī)避了潛在錯(cuò)誤的發(fā)生。

四十二、慎用可變參數(shù):

? ? ? 可變參數(shù)方法接受0個(gè)或者多個(gè)指定類型的參數(shù)。可變參數(shù)機(jī)制通過先創(chuàng)建一個(gè)數(shù)組,數(shù)組的大小為在調(diào)用位置所傳遞的參數(shù)數(shù)量,然后將參數(shù)值傳到數(shù)組中,最后將數(shù)組傳遞給方法,如:

        
          1
        
        
          static
        
        
          int
        
         sum(
        
          int
        
        ...args) {


        
          2
        
        
          int
        
         sum = 0;


        
          3
        
        
          for
        
         (
        
          int
        
         arg : args)


        
          4
        
                     sum += arg;

        
          
5
        
                 retrun sum;


        
          6
        
             }
      

? ? ? 上面的方法可以正常的工作,但是在有的時(shí)候,我們可能需要至少一個(gè)或者多個(gè)某種類型參數(shù)的方法,如:

1 ???? static int min( int ...args) {
2 ???????? if (args.length == 0)
3 ???????????? throw new IllegalArgumentException("Too few arguments.");
4 ???????? int min = args[0];
5 ???????? for ( int i = 0; i < args.length; ++i) {
6 ???????????? if (args[i] < min)
7 ???????????????? min = args[i];
8 ???????? }
9 ???????? return min;
10 ???? }
?

? ? ? 對(duì)于上面的代碼主要存在兩個(gè)問題,一是如果調(diào)用者沒有傳遞參數(shù)是,該函數(shù)將會(huì)在運(yùn)行時(shí)拋出異常,而不是在編譯期報(bào)錯(cuò)。另一個(gè)問題是這樣的寫法也是非常不美觀的,函數(shù)內(nèi)部必須做參數(shù)的數(shù)量驗(yàn)證,不僅如此,這也影響了效率。將編譯期可以完成的事情推到了運(yùn)行期。下面提供了一種較好的修改方式,如下:

1 ???? static int min( int firstArg, int ...remainingArgs) {
2 ???????? int min = firstArgs;
3 ???????? for ( int arg : remainingArgs) {
4 ???????????? if (arg < min)
5 ???????????????? min = arg;
6 ???????? }
7 ???????? return min;
8 ???? }
?

? ? ? 由此可見,當(dāng)你真正需要讓一個(gè)方法帶有不定數(shù)量的參數(shù)時(shí),可變參數(shù)就非常有效。
? ? ? 有的時(shí)候在重視性能的情況下,使用可變參數(shù)機(jī)制要特別小心。可變參數(shù)方法的每次調(diào)用都會(huì)導(dǎo)致進(jìn)行一次數(shù)組分配和初始化。如果確定確實(shí)無(wú)法承受這一成本,但又需要可變參數(shù)的靈活性,還有一種模式可以彌補(bǔ)這一不足。假設(shè)確定對(duì)某個(gè)方法95%的調(diào)用會(huì)有3個(gè)或者更少的參數(shù),就聲明該方法的5個(gè)重載,每個(gè)重載方法帶有0個(gè)至3個(gè)普通參數(shù),當(dāng)參數(shù)的數(shù)目超過3個(gè)時(shí),就使用一個(gè)可變參數(shù)方法:

        
          1
        
        
          public
        
        
          void
        
         foo() {}


        
          2
        
        
          public
        
        
          void
        
         foo(
        
          int
        
         a1) {}


        
          3
        
        
          public
        
        
          void
        
         foo(
        
          int
        
         a1,
        
          int
        
         a2) {}

        
          
4
        
        
          public
        
        
          void
        
         foo(
        
          int
        
         a1,
        
          int
        
         a2,
        
          int
        
         a3) {}


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

? ? ? 所有調(diào)用中只有5%參數(shù)數(shù)量超過3個(gè)的調(diào)用需要?jiǎng)?chuàng)建數(shù)組。就像大多數(shù)的性能優(yōu)化一樣,這種方法通常不恰當(dāng),但是一旦真正需要它時(shí),還是非常有用處的。
?? ?
四十三、返回零長(zhǎng)度的數(shù)組或者集合,而不是null:

? ? ? 見如下代碼:

復(fù)制代碼
1 ???? public class CheesesShop {
2 ???????? private final List<Cheese> cheesesInStock = new List<Cheese>();
3 ???????? public Cheese[] getCheeses() {
4 ???????????? if (cheesesInStock.size() == 0)
5 ???????????????? return null ;
6 ???????????? return cheeseInStock.toArray( null );
7 ???????? }
8 ???? }
?

?? ?? 從以上代碼可以看出,當(dāng)沒有Cheese的時(shí)候,getCheeses()函數(shù)返回一種特例情況null。這樣做的結(jié)果會(huì)使所有的調(diào)用代碼在使用前均需對(duì)返回值數(shù)組做null的判斷,如下:

        
          1
        
        
          public
        
        
          void
        
         testGetCheeses(CheesesShop shop) {


        
          2
        
                 Cheese[] cheeses = shop.getCheeses();


        
          3
        
        
          if
        
         (cheese != 
        
          null
        
         && Array.asList(cheeses).contains(Cheese.STILTON))

        
          4
        
                     System.out.println("Jolly good, just the thing.");


        
          5
        
             }
      

? ? ? 對(duì)于一個(gè)返回null而不是零長(zhǎng)度數(shù)組或者集合的方法,幾乎每次用到該方法時(shí)都需要這種曲折的處理方式。很顯然,這樣是比較容易出錯(cuò)的。如果我們使getCheeses()函數(shù)在沒有Cheese的時(shí)候不再返回null,而是返回一個(gè)零長(zhǎng)度的數(shù)組,那么我的調(diào)用代碼將會(huì)變得更加簡(jiǎn)潔,如下:

        
          1
        
        
          public
        
        
          void
        
         testGetCheeses2(CheesesShop shop) {


        
          2
        
        
          if
        
         (Array.asList(shop.getCheeses()).contains(Cheese.STILTON))


        
          3
        
                     System.out.println("Jolly good, just the thing.");


        
          4
        
             }
      

? ? ? 相比于數(shù)組,集合亦是如此。

Effective Java (方法)


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 神池县| 北流市| 斗六市| 浙江省| 梁河县| 米易县| 三门县| 普安县| 静乐县| 华蓥市| 海原县| 博兴县| 凉山| 安义县| 岗巴县| 贵州省| 繁峙县| 舟山市| 斗六市| 吉隆县| 阿坝| 新泰市| 绵竹市| 贵州省| 安吉县| 简阳市| 内乡县| 监利县| 久治县| 汤阴县| 巫溪县| 南汇区| 昌平区| 巨野县| 西华县| 衢州市| 津市市| 射洪县| 铅山县| 蒲江县| 寿光市|