不可变类

  顾名思义,一个类实例化一个对象后,对象的属性没法被改变,可称之为不可变类。如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

  只要作到这三点,基本上就能保证你的类为不可变类了。但要想破解依然能够作到,作法相似破解单例,能够用反射来改变属性的私有为公有,这里就不展开了。对象

相关文章
相关标签/搜索