第50條:必要時(shí)進(jìn)行保護(hù)性拷貝

當(dāng)我們?cè)谠O(shè)計(jì)一個(gè)不可變類,要注意保證它的組件也是不可變的,因此要進(jìn)行保護(hù)性拷貝。尤其是類的可變組件是來(lái)自于客戶端時(shí),盡管我們可以跟客戶端約定不會(huì)修改這些組件,但是都不如保護(hù)性拷貝來(lái)的保險(xiǎn)。


  • 1.在設(shè)計(jì)不可變類時(shí),不可變組件是首選
    使用不可變的類作為我們新設(shè)計(jì)的類的組件,那么從類自身的屬性上就可以確保它是不可變的了。如Instant、LocalTimeDate或ZonedDateTime 就優(yōu)于Date.

  • 2.實(shí)在需要使用可變類時(shí),首先應(yīng)該在構(gòu)造器中進(jìn)行保護(hù)性拷貝
    如這個(gè)Period 類的例子:

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

上面這種沒有保護(hù)性拷貝的構(gòu)造方法,就有可能在初始化之后,被客戶端修改內(nèi)部Date 類的對(duì)象:

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); /

使用保護(hù)性拷貝的構(gòu)造方法如下:

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(this.start + " after " + this.end);
}

可以避免上述問(wèn)題。

這里需要注意的地方是,如果有入?yún)⑿r?yàn),應(yīng)該在對(duì)拷貝后的對(duì)象校驗(yàn),否則在校驗(yàn)前,對(duì)象可能就已經(jīng)被其他程序修改了。

  • 3.還需要對(duì)類中可以訪問(wèn)到可變組件的方法進(jìn)行保護(hù)性拷貝
    如這里的Date 的get、set 方法。
    如果不進(jìn)行保護(hù)性拷貝,那么客戶端還是有可能通過(guò)這些方法更新可變組件,如getEnd().setYear(72).
    因此還要注意把這些方法進(jìn)行保護(hù)性拷貝:
// Repaired accessors - make defensive copies of internal fields
public Date start() {
    return new Date(start.getTime());
}
public Date end() {
    return new Date(end.getTime());
}

保護(hù)性拷貝是在設(shè)計(jì)不可變類時(shí),存在可變組件時(shí)的一種技巧。其核心思路是:把不可變類的可變組件隱藏起來(lái),客戶端想訪問(wèn)時(shí),只提供一個(gè)“”替身“”,從而避免客戶端修改到這些組件的狀態(tài)。
讀這部分內(nèi)容時(shí)讓我聯(lián)想起了兩部分內(nèi)容:
一是對(duì)數(shù)組的運(yùn)算,因?yàn)閿?shù)組和列表總是可變的,有時(shí)我們?cè)谟?jì)算時(shí)為了保證一個(gè)數(shù)組的結(jié)構(gòu)和其內(nèi)部對(duì)象的狀態(tài)不變,會(huì)選擇拷貝出一個(gè)新的數(shù)組或列表來(lái)操作。
二是建造者模式,如MsgBuilder。 builder 往往是根據(jù)一個(gè)或多個(gè)對(duì)象來(lái)組裝出自己想要的對(duì)象。如UserMsgBuilder(User user)。UserMsgBuilder 本身不應(yīng)該修改到user 的狀態(tài)。(當(dāng)然這里可能不太需要真的把User 設(shè)計(jì)成不可變類,更多是一種約定)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容