1. 程式人生 > >effectiveJava學習筆記:方法(一)

effectiveJava學習筆記:方法(一)

檢查引數的有效性

1、一般在方法執行之前先檢查引數的有效性,如果引數值無效,那麼很快它就會失敗,並且清楚的丟擲合適的異常。

如果這個方法沒有檢查引數的異常,那麼可能在方法處理中出現令人費解的異常。更糟糕的有可能是,方法可以正常返回,但是卻使得某個物件處於被破壞的狀態.

2、對於公有方法,可以在Javadoc中的@throw標籤來說明違反異常時所丟擲的異常型別

3、非公有方法通常應該使用斷言(assert)來檢查它們的引數(在生產環境中,一般是不支援assert的,因此這樣可以提高效率,沒有成本開銷。所以,assert只在私有方法中使用,因為私有方法的呼叫者開發者,他和被呼叫者之間是一種弱契約關係,或者說沒有契約關係,其間的約束是依靠開發者自己控制的,開發者應該有充分的理由相信自己傳入的引數是有效的。所以,從某種角度上來說,assert只是起到一個預防開發者自己出錯,或者是程式的無意出錯)

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

4、有一些引數暫時沒有直接用到,只是儲存起來供以後使用,這種引數的有效性檢查也是尤其重要

    static List<Integer> intArrayAsList(final int[] a) {
        if (a == null)
            throw new NullPointerException();
 
        return new AbstractList<Integer>() {
            public Integer get(int i) {
                return a[i];  // Autoboxing (Item 5)
            }
 
            @Override public Integer set(int i, Integer val) {
                int oldVal = a[i];
                a[i] = val;     // Auto-unboxing
                return oldVal;  // Autoboxing
            }
 
            public int size() {
                return a.length;
            }
        };
    }

5、儘管在構造器中檢查引數的有效性非常必要,但是也有例外,可能在有效情況下檢查引數的有效性是及其昂貴的,甚至是不切實際的。

並非對引數的任何限制都是好事,一般來說要儘可能的通用, 符合實際的需要。假如方法對它能接受的引數都能完成合理的計算,那麼對於引數的限制其實是越少越好的。因此,鼓勵開發者把限制寫到文件中,並在方法的開頭顯式的檢查引數的有效性。

 

必要時進行保護性拷貝

容易被破壞的內部約束條件

雖然如果沒有主動提供公共方法和變數,外部是無法修改類內部的資料的。但是,物件可能會在無意識的情況下提供幫助。例如,下面就是一個通過引用來修改類內部的資料,而破壞物件內部的約束條件的例子:

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

雖然沒有p.setEnd()方法修改內部資訊,但是通過Date的引用,我們可以修改Person內部的資訊

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

對構造器的每個可變引數進行保護性拷貝

package com.ligz.Chapter7.Item39;

import java.util.Date;

/**
 * @author ligz
 */
public class Period {
	private final Date start;
	private final Date end;
	
	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);

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

值得注意的是,這裡用的是獲取需要的Date資料來進行拷貝,才不是使用clone進行拷貝,是因為,Date本身不是final的,不能保證返回的一定是一個安全的java.util.Date類。

在這種方法中,構造器中不直接接受原物件的引用。而是,對原物件中的資料進行拷貝,使用備份物件作為Period例項的元件。

通過保護性拷貝,避免通過直接改變引用和獲取返回值的引用,間接修改Period內的資料。

當編寫方法和構造器的時候,如果允許客戶端提供的物件進入到內部的資料,就需要考慮,客戶端的物件是否是可變,它的可變是否會對內部資料產生影響?!!

 

 

謹慎設計方法簽名

1、方法的名稱易於理解、與其他的風格一致。

2、一個類當中的方法不要太多,當一項操作被頻繁使用,才選擇為他提供快捷方式。

3、避免使用引數過長的方法,引數的列表應該小於四個。使用三種方法可以避免參數過長(1.把方法分解成多個方法;2.建立輔助類來儲存引數的分組,一般為靜態成員;3.結合兩種使用Builder模式,尤其適合可選引數,這個在我之前的部落格裡面有—類的建立)

3、對於引數型別優先使用介面而不是類。就像在方法中不會使用HashMap類作為輸入,但是會使用Map介面作為引數。

4、對於Boolean引數,優先使用兩個元素的列舉型別。(沒看懂。。。下次有機會再掙扎一下)