【07】Effective Java - 方法

一、檢查參數的有效性

(1)防衛式校驗

 在方法開頭校驗入參的合法性html

/**
     * @param  start the beginning of the period
     * @param  end the end of the period; must not precede start
     * @throws IllegalArgumentException if start is after end
     * @throws NullPointerException if start or end is null
     */
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0)
            throw new IllegalArgumentException(
                start + " after " + end);
        this.start = start;
        this.end   = end;
    }

(2)異常轉義

可使用assert,但必要時須要異常轉義,本身拋自定義異常java


二、必要時進行保護性拷貝

(1)保護性拷貝
// Repaired constructor - makes defensive copies of parameters - Page 185
    // Stops first attack
//  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);
//  }

  // Repaired accessors - make defensive copies of internal fields - Page 186
    // Stops second attack
//  public Date start() {
//      return new Date(start.getTime());
//  }
//
//  public Date end() {
//      return new Date(end.getTime());
//  }

public class Attack {
    public static void main(String[] args) {
        // Attack the internals of a Period instance - Page 185
        Date start = new Date();
        Date end = new Date();
        Period p = new Period(start, end);
        end.setYear(78);  // Modifies internals of p!
        System.out.println(p);

        // Second attack on the internals of a Period instance - Page 186
        start = new Date();
        end = new Date();
        p = new Period(start, end);
        p.end().setYear(78);  // Modifies internals of p!
        System.out.println(p);
    }
}

    Date類型可變,於是返回一個新對象,而不使用原始對象數組

(2)在檢查參數有效性以前進行

    有效性檢查,針對拷貝後的進行檢查,能夠防止多線程時出現錯誤,拷貝後的纔是類本身的。這樣能夠防止從檢查參數開始到拷貝參數之間的時間段裏頭,有另一個線程改變類的參數。安全

(3)容器實例
public Collection<User> getUsers(){
   return Collections.unmodifiableCollection(originalCollection);
}


三、謹慎設計方法簽名

(1)遵循命名規範

A、易於理解多線程

B、與大衆一致ide


(2)不要過於追求提供便利方法

方法太多則會使類難以學習、維護、測試post


(3)避免過長的參數列表

目標是四個參數或更少,相同類型的參數容易出錯,特別是用戶混淆了其順序。性能

縮短參數個數的方法:學習

A、把方法拆解爲多個
B、建立輔助類
C、採用Builder模式
(4)參數類型優先是使用接口而不是類

 好比用Map,替代HashMap做爲方法參數類型測試


(5)對於boolean參數優先使用兩個元素的枚舉類型

方便閱讀

public enum TemperatureScale{ FAHRENHEIT,CELSIUS}


四、慎用重載

(1)重載是例外
public class CollectionClassifier {
    public static String classify(Set<?> s) {
        return "Set";
    }

    public static String classify(List<?> lst) {
        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));
    }
}

#輸出Unknown Collection

   建議,永遠不要導出兩個具備相同參數數目的重載方法,對於可變參數的方法,不要去重載它。


(2)覆蓋是規範
class Wine {
    String name() { return "wine"; }
}

class SparklingWine extends Wine {
    @Override String name() { return "sparkling wine"; }
}

class Champagne extends SparklingWine {
    @Override String name() { return "champagne"; }
}

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

   覆蓋可以按照預期輸出。

(3)重載方法的選擇
public class SetList {
    public static void main(String[] args) {
        Set<Integer> set = new TreeSet<Integer>();
        List<Integer> list = new ArrayList<Integer>();

        for (int i = -3; i < 3; i++) {
            set.add(i);
            list.add(i);
        }

        for (int i = 0; i < 3; i++) {
            set.remove(i);
            list.remove(i);
        }

        System.out.println(set + " " + list);
    }
}

   set.remove(i)調用選擇了重載方法remove(E),於是輸出符合預期;

   list.remove(i)調用選擇了重載方法remove(int i),於是,其根據下標刪除,不符合預期,須要改成

list.remove(Integer.valueOf(i));
或者
list.remove((Integer)i);

   儘可能作到,當傳遞一樣的參數時,全部重載方法的行爲必須一致。


五、慎用可變參數

(1)變參誤用
public class Varargs {

    // Simple use of varargs - Page 197
    static int sum(int... args) {
        int sum = 0;
        for (int arg : args)
            sum += arg;
        return sum;
    }


    // The WRONG way to use varargs to pass one or more arguments! - Page 197
//  static int min(int... args) {
//      if (args.length == 0)
//          throw new IllegalArgumentException("Too few arguments");
//      int min = args[0];
//      for (int i = 1; i < args.length; i++)
//          if (args[i] < min)
//              min = args[i];
//      return min;
//  }

    // The right way to use varargs to pass one or more arguments - Page 198
    static int min(int firstArg, int... remainingArgs) {
        int min = firstArg;
        for (int arg : remainingArgs)
            if (arg < min)
                min = arg;
        return min;
    }

    public static void main(String[] args) {
        System.out.println(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        System.out.println(min(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    }
}

(2)println變參
#wrong way to println array
System.out.println(Arrays.asList(myArray));
#right way 
System.out.println(Arrays.toString(myArray));


(3)性能問題

    可變參數方法的每次調用都會致使進行一次數組的分配和初始化,若是沒法承受這一成本,就聲明該方法的幾個重載方法。


六、返回零長度的數組或集合而非null

(1)返回null,須要客戶端額外判斷
public Cheese[] getCheeses(){
   if(cheesesInStack.size() == 0){
      return null;
   }
}

(2)返回零長度數組的性能問題
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];

public Cheese[] getCheese(){
   return cheesesInStack.toArray(EMPTY_CHEESE_ARRAY);
}

  有人認爲返回null比零長度數組好,由於避免了分配數組所須要的開銷。這種觀點是站不住腳的。

A、在這個級別上擔憂性能分配問題是不明智的,除非分析代表這個方法正是形成性能問題的根源

B、對於不返回任何元素的調用,每次都返回同一個零長度數組是有可能的,由於零長度數組是不可變的,能夠自由共享。

   實際上,Collection.toArray(T[])的規範保證了,若是輸入數組大到足夠容納這個集合,則返回這個輸入數組。所以這種作法永遠也不會分配零長度的數組。

  一樣的,Collections.emptyList(),emptySet(),emptyMap()正是你所須要的。


七、爲全部導出的API元素編寫文檔註釋

(1)方法文檔註釋要簡潔描述出與客戶端的約定

A、前提條件precondition

     通常由@throw隱含描述

B、後置條件postcondition

    調用成功後哪些條件被知足

C、反作用SideEffect

    系統狀態的變化,是否線程安全等

D、參數和返回值類型及說明


(2)@code,@literal避免html轉義
<pre>
@code  your code here
<pre>

this is {@literal |x+y| < |x| + |y|}

(3)枚舉和註解的註釋

A、枚舉中要註明常量

B、註解要說明其成員及類型


(4)成員註釋與方法註釋

A、成員註釋,採用單行的 /** here */

B、方法註釋,採用多行的

    /**

     *

     */

相關文章
相關標籤/搜索