《Effective Java》讀書筆記 —— 方法

本文大多數(shù)內(nèi)容適用于構(gòu)造器吭练,也適用于普通方法诫龙,焦點(diǎn)集中在可用性析显、健壯性和靈活性上鲫咽。

1.檢查參數(shù)的有效性

一個原則:應(yīng)該在發(fā)生錯誤之后盡快檢測出錯誤

不檢查參數(shù)有效性的后果:

  • 方法在處理的過程中失敗签赃,并且產(chǎn)生令人費(fèi)解的異常
  • 方法正常返回,但計算出錯誤的結(jié)果
  • 方法正常返回分尸,但卻使得某個對象處于被破壞的狀態(tài)锦聊,將來在某個不確定的時候引發(fā)錯誤

常見方法參數(shù)的一些限制:

  • 索引值必須是非負(fù)數(shù)
  • 集合類的索引不能大于集合長度-1
  • 對象引用不能為 null

public 方法參數(shù)有效性檢查

步驟:

  1. 用 Javadoc 的@throw標(biāo)簽在文檔中說明違法參數(shù)值限制時拋出的異常,異常通常為 IllegalArgumentException箩绍、IndexOutOfBoundsException孔庭、NullPointerException等
  2. 公有方法內(nèi)部進(jìn)行參數(shù)有效性檢查,并拋出相應(yīng)異常
    /**
     * Returns a BigInteger whose value is.
     * @param m
     * @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
    }

private/package-private 方法參數(shù)有效性檢查

非公有方法通常應(yīng)該使用斷言來檢查它們的參數(shù)有效性

斷言與普通有效性檢查的區(qū)別:

  • 斷言如果失敗材蛛,拋出 AssertionError
  • 如果它們沒起到作用圆到,本質(zhì)上也不會有成本開銷
    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;
        // next
    }

構(gòu)造方法參數(shù)有效性檢查

對于有些參數(shù),方法本身沒有用到卑吭,但被保存起來供以后使用芽淡。比如靜態(tài)工廠方法、構(gòu)造方法豆赏。檢查參數(shù)有效性非常重要挣菲,避免構(gòu)造出來的對象違反了這個類的約束條件

不需要檢查參數(shù)有效性的情況

  • 有效性檢查很昂貴,或者不切實際贺奠,比如 sort 方法谁尸,檢查集合中每一個對象是否可以比較
  • 計算會隱式執(zhí)行必要的有效性檢查陈醒,比如 sort 方法,如果對象不能比較或杠,就會拋出 ClassCastException

總結(jié)

  • 編寫方法前,考慮好它的參數(shù)有哪些限制
  • 把限制寫到方法開頭的文檔中
  • 通過顯式的檢查來實施限制

2.必要時進(jìn)行保護(hù)性拷貝

先介紹一個概念宣蔚,不可變性廷痘,之前介紹過,要盡可能創(chuàng)建不可變的類件已,因為它有很多優(yōu)點(diǎn)笋额,其中創(chuàng)建不可變類有幾條規(guī)則:
- 如果類具有指向可變對象的域,必須確保該類的客戶端無法獲得執(zhí)行這些對象的引用
- 在構(gòu)造器中篷扩,永遠(yuǎn)不要用客戶端提供的對象引用來初始化這樣的域
- 在訪問方法中兄猩,也不要返回該對象引用
- 在構(gòu)造器、訪問方法和 readObject 方法中請使用保護(hù)性拷貝技術(shù)

創(chuàng)建 Period 類鉴未,由于Date類是可變的枢冤,所以外部可能拿到內(nèi)部的 start 和 end 信息,進(jìn)而會修改這個信息

    public final class Period {
        private final Date start;
        private final Date end;

        public Period(Date start, Date end) {
            this.start = start;
            this.end = end;
        }
        
        public Date start() {
            return start;
        }
        
        public Date end() {
            return end;
        }
    }

對于構(gòu)造器的每個可變參數(shù)進(jìn)行保護(hù)性拷貝

為了避免內(nèi)部信息被攻擊铜秆,對于構(gòu)造器的每個可變參數(shù)進(jìn)行保護(hù)性拷貝淹真,創(chuàng)建新的對象,而不是使用客戶端傳入的對象

    public Period(Date start, Date end) {
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());
    }

注意:

  • 保護(hù)性拷貝是在檢查參數(shù)有效性之前進(jìn)行
  • 保護(hù)性拷貝连茧,沒有使用 clone 方法核蘸,因為 Date 是非 final 的巍糯,clone 方法不能保證一定會返回 Date 對象,可能返回出于惡意目的而設(shè)計的不可信子類的實例

使訪問方法返回可變內(nèi)部域進(jìn)行保護(hù)性拷貝

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

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

總結(jié)

  • 參數(shù)(和返回值)的保護(hù)性拷貝不僅僅針對不可變類客扎,每當(dāng)允許客戶提供的對象進(jìn)入內(nèi)部數(shù)據(jù)結(jié)構(gòu)中祟峦,則有必要考慮一下,客戶提供的對象是否有可能是變化的
  • 長度非零的數(shù)組總是可變的徙鱼,在把內(nèi)部數(shù)組返回給客戶端時宅楞,總要進(jìn)行保護(hù)性拷貝
  • 真正的啟示:盡可能使用不可變對象,不必再擔(dān)心保護(hù)性拷貝
  • 對于Date袱吆,通常不要直接使用Date的引用厌衙,而是使用Date.getTime()返回的long基本類型作為時間的表示,防止Date對象的可變性導(dǎo)致的問題

最后绞绒,如果類具有從客戶端得到或者返回到客戶端的可變組件迅箩,類就必須保護(hù)性地拷貝這些組件,如果拷貝的成本受到限制处铛,并且類信任它的客戶端不會不恰當(dāng)?shù)匦薷慕M件饲趋,就可以在文檔中指明客戶端的職責(zé)是不得修改受到影響的組件,以此來代替保護(hù)性拷貝撤蟆。

3.謹(jǐn)慎設(shè)計方法簽名

API 設(shè)計技巧總結(jié):

  • 謹(jǐn)慎地選擇方法的名稱
    • 易于理解
    • 與同一個包中的其他名稱風(fēng)格一致的名稱
    • 選擇與大眾認(rèn)可的名稱
  • 不要過于追求提供便利的方法
  • 避免過長的參數(shù)列表
    • 四個參數(shù)或更少

縮短長參數(shù)列表的方式:

  • 把方法分解成多個方法奕塑,每個方法只需要這些參數(shù)的一個子集
  • 創(chuàng)建輔助類,用來保存參數(shù)的分組家肯,一般是靜態(tài)成員類龄砰,如果一個頻繁出現(xiàn)的參數(shù)序列可以被看作代表了某個獨(dú)特實體,則建議使用這種方式
  • 使用Builder模式讨衣,參數(shù)很多换棚,且有些是可選的。

類參數(shù)的使用技巧:

  • 對于參數(shù)類型反镇,要優(yōu)先使用接口而不是類
    • 比如固蚤,沒有理由使用 HashMap 作為參數(shù),應(yīng)當(dāng)使用 Map 接口作為參數(shù)
  • 對于 boolean 參數(shù)歹茶,要優(yōu)先使用兩個元素的枚舉類型
    • 代碼更易于閱讀和編寫

4.慎用重載

重載方法的選擇是靜態(tài)的夕玩,是在編譯時做出決定的,只能調(diào)用與此明確對應(yīng)的重載方法惊豺,而不是其父類或者子類燎孟。與此不同的是覆蓋方法,覆蓋方法是動態(tài)的尸昧,是在運(yùn)行時決定要調(diào)用子類還是父類的方法揩页。

看一個例子。

private class CollectionClassifier {
    
    public static String classify(Set<?> s) {
        return "Set";
    }

    public static String classify(List<?> s) {
        return "List";
    }

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

結(jié)果烹俗,打印了三次 “Unknown Collection”爆侣,對于for循環(huán)的三次迭代萍程,只能調(diào)用在編譯時確定的參數(shù)為 Collection<?> 的方法。

解決方案:用單個方法替換三個重載方法

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

普通方法避免重載

具體累提,對于write方法,如果就有變形磁浇,不應(yīng)該使用重載斋陪,而是增加諸如writeBoolean、writeInte這樣的簽名方法置吓。

構(gòu)造器方法避免重載

不能使用不同名稱的構(gòu)造器无虚,但可以選擇導(dǎo)出靜態(tài)工廠,而不是重載構(gòu)造器

總結(jié)

“能夠重載方法”并不意味著“應(yīng)該重載方法”衍锚。一般情況下友题,對于多個具有相同參數(shù)數(shù)目的方法來說,應(yīng)該盡量避免重載方法戴质。

5.慎用可變參數(shù)

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

可變參數(shù)性能問題:可變參數(shù)方法的每次調(diào)用功能會導(dǎo)致進(jìn)行一次數(shù)組分配和初始化划鸽。

只有對于參數(shù)數(shù)目不確定的情況,才會使用可變參數(shù)戚哎。

6.返回零長度的數(shù)組或者集合裸诽,而不是 null

對于一個返回null而不是零長度的數(shù)組或者集合的方法,編寫客戶端的程序員很可能會忘記這種專門的代碼來處理null返回值型凳。

返回零長度數(shù)組不會增加開銷丈冬,零長度數(shù)組是不可變的,是自由共享的甘畅。

7.為所有導(dǎo)出的API元素編寫文檔注釋

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末殷蛇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子橄浓,更是在濱河造成了極大的恐慌粒梦,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荸实,死亡現(xiàn)場離奇詭異匀们,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)准给,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門泄朴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來重抖,“玉大人,你說我怎么就攤上這事祖灰≈优妫” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵局扶,是天一觀的道長恨统。 經(jīng)常有香客問我,道長三妈,這世上最難降的妖魔是什么畜埋? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮畴蒲,結(jié)果婚禮上悠鞍,老公的妹妹穿的比我還像新娘。我一直安慰自己模燥,他們只是感情好咖祭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蔫骂,像睡著了一般心肪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纠吴,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天硬鞍,我揣著相機(jī)與錄音,去河邊找鬼戴已。 笑死固该,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的糖儡。 我是一名探鬼主播伐坏,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼握联!你這毒婦竟也來了桦沉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤金闽,失蹤者是張志新(化名)和其女友劉穎纯露,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體代芜,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡埠褪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钞速。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡贷掖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出渴语,到底是詐尸還是另有隱情苹威,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布驾凶,位于F島的核電站牙甫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狭郑。R本人自食惡果不足惜腹暖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一汇在、第九天 我趴在偏房一處隱蔽的房頂上張望翰萨。 院中可真熱鬧,春花似錦糕殉、人聲如沸亩鬼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雳锋。三九已至,卻和暖如春羡洁,著一層夾襖步出監(jiān)牢的瞬間玷过,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工筑煮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辛蚊,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓真仲,卻偏偏與公主長得像袋马,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子秸应,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容