不可變類

  顧名思義,一個類實例化一個對象後,對象的屬性沒法被改變,可稱之爲不可變類。如JDK中的八大包裝類、String類等。不可變類各有用處,如包裝類用於對基本類型的裝箱操做,把基本類型化身爲對象使用。而String類做爲咱們最經常使用的類之一,經過字符串常量池大大提高了性能。不可變類由於是不可變的,因此自然具備線程安全性。那麼如何定義一個類爲不可變類呢?java

  首先,關於類的成員變量(也就是屬性),若是它們都不是不可變類,那麼它們應該是私有的、final的。經過私有的封裝來讓屬性外部沒法修改,而final的做用是讓屬性初始化後就不能再改變;安全

  其次,關於類的方法,咱們不能提供任何修改屬性是方法,好比常見的setXXX方法就不能再出現了。若是類成員變量自己不是不可變的,那麼須要注意兩點:一、在初始化(好比構造器裏)給該成員變量賦值時,應該取的是外部對象引用的克隆;二、若是不可變類提供給外部用於獲取該成員變量的方法時,應該使用該成員變量的克隆,而非直接返回成員變量自己。這兩點的目的都是避免外部經過對象引用修改不可變類的內部成員變量。詳見下面例子:ide

import java.util.Date;

public class Immutable {
    private String name;
    private final Date date;

    public Immutable(String name, Date date) {
        this.name = name;
        this.date = date;
    }

    public String getName() {
        return name;
    }

    public Date getDate() {
        return date;
    }

    @Override
    public String toString() {
        return "Immutable{" +
                "name='" + name + '\'' +
                ", date=" + date +
                '}';
    }

    public static void main(String[] args) {
        String name = "wlf";
        Date today = new Date();

        Immutable t = new Immutable(name, today);
        System.out.println(t.toString());

        // 構造器初始化方法漏洞
        name = "wms";
        today.setTime(today.getTime() + 100000);
        System.out.println("Change: " + t.toString());

        // get方法漏洞
        name = t.name;
        today = t.getDate();
        name = "hello";
        today.setTime(today.getTime() + 100000);
        System.out.println("Change: " + t.toString());
    }
}

  運行結果:性能

Immutable{name='wlf', date=Mon Jun 03 22:55:10 CST 2019}
Change: Immutable{name='wlf', date=Mon Jun 03 22:56:50 CST 2019}
Change: Immutable{name='wlf', date=Mon Jun 03 22:58:30 CST 2019}

  咱們看到String是不可變類,因此咱們很放心,而Date並不是不可變類,因此它變了。咱們經過克隆來讓對象的引用不被外部操縱:this

import java.util.Date;

public class Immutable {
    private String name;
    private final Date date;

    public Immutable(String name, Date date) {
        this.name = name;
        this.date = (Date) date.clone();
    }

    public String getName() {
        return name;
    }

    public Date getDate() {
        return (Date) date.clone();
    }

    @Override
    public String toString() {
        return "Immutable{" +
                "name='" + name + '\'' +
                ", date=" + date +
                '}';
    }

    public static void main(String[] args) {
        String name = "wlf";
        Date today = new Date();

        Immutable t = new Immutable(name, today);
        System.out.println(t.toString());

        // 構造器初始化方法漏洞
        name = "wms";
        today.setTime(today.getTime() + 100000);
        System.out.println("Change: " + t.toString());

        // get方法漏洞
        name = t.name;
        today = t.getDate();
        name = "hello";
        today.setTime(today.getTime() + 100000);
        System.out.println("Change: " + t.toString());
    }
}

  運行結果:spa

Immutable{name='wlf', date=Mon Jun 03 22:57:16 CST 2019}
Change: Immutable{name='wlf', date=Mon Jun 03 22:57:16 CST 2019}
Change: Immutable{name='wlf', date=Mon Jun 03 22:57:16 CST 2019}

  上面咱們看到,克隆爲咱們保證了不可變性,它是如何作到的?克隆就像分身術同樣,實際上就是針對原生對象,克隆了一個分身對象出來。線程

  最後,關於自己,爲了防止子類複寫父類的方法從而破壞不可變性,咱們把類定義爲final的。code

  只要作到這三點,基本上就能保證你的類爲不可變類了。但要想破解依然能夠作到,作法相似破解單例,能夠用反射來改變屬性的私有爲公有,這裏就不展開了。對象

相關文章
相關標籤/搜索