天天看点

《Effective java》读书记录-第15条-使可变性最小化

不可变类是,其实例不能修改的类。每个实例所包含的信息都必须在创建该实例的时候提供,并在对象的整个生命周期内固定不变。

使类变成不可变类应该遵循的5条规则:

1.不要提供任何会修改对象状态的方法(mutator)。

2.保证类不会被扩展。

这样可以防止粗心或者恶意的子类假装对象的状态已经改变,从而破坏该类的不可变行为。防止子类化,一般做法是使这个类成为final的,后续还会讨论其他做法。

3.使所有的域都是final的。

4.使所有的域都成为私有的。

这也可以防止客户端获得访问被域所引用的可变对象的权限,并防止客户端直接修改这些对象。

5.确保对于任何可变组件的互斥访问。

如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。

/**
 * 这是一个复数类
 * Aouther: sunyx
 * Date: 2016/1/11
 * Time: 16:39
 */
public class Complex {

    private final double re;//实部
    private final double im;//虚部

    /**
     * 创建一个新的实例  Complex
     *
     *
     */
    public Complex (double re,double im) {
        this.re=re;
        this.im=im;
    }

    //Accessor with no corresponeding mutators
    public double realPart(){return re; }

    public double imaginaryPart(){ return im;}

    /**
     * 方法都是返回新的Complex实例,而不是修改这个实例。
     * 大多数重要的不可变类都使用这种模式
     * @param c
     * @return
     */
    public Complex add(Complex c){
        return new Complex(re+c.re,im+c.im);
    }

    public Complex subtract(Complex c){
        return  new Complex(re-c.re,im-c.im);
    }

    public Complex multiply(Complex c){
        return  new Complex(re*c.re-im*c.im,
                re*c.im+im*c.re);
    }

    public Complex divide(Complex c){
        double tmp=c.re*c.re+c.im*c.im;
        return  new Complex(
                (re*c.re+im*c.im)/tmp,
                (im*c.re-re*c.im)/tmp);
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == this)return true;
        if(!(obj instanceof  Complex)) return false;
        Complex c= (Complex) obj;
        //See page 43 to find out why we use compare instead off ==
        return Double.compare(re,c.re)==0 && Double.compare(im,c.im)==0;
    }

    @Override
    public int hashCode() {
        int result=17+hashDouble(re);
        result=31*result+hashDouble(im);
        return result;
    }

    private int hashDouble(double val){
        long longBits =Double.doubleToLongBits(re);
        return (int)(longBits ^ ( longBits >>> 32 ));
    }

    @Override
    public String toString() {
        return "("+re+"+"+im+"i)";
    }
}
           

Complex类中的加减乘除的方法都是返回一个Complex实例,这中做法称作函数式做法(函数式语言的处理方式,在函数式语言中返回值是常量,是不可以修改的)。

不可变对象比较简单。不可变对象本质上是线程安全的,它们不要求同步。当多线程并发访问这样的对象时,它们不会遭到破坏。这毫无疑问是获得线程安全最容易的方法。不可变对象可以自由的被共享。不可变类鼓励客户端尽可能地重用现有的实例。对于频繁用到的值,可以考虑为它们提供公有的静态final常量。

public static final Complex ZERO=new Complex(0,0);
    public static final Complex ONE=new Complex(1,0);
    public static final Complex I=new Complex(0,1);
           

这个方法可以被扩展一下,利用静态工厂方法,把频繁被请求的实例缓存起来。所有基本类型的包装类和BigInteger都有这样的静态工厂。

不可变对象可以自由的被共享。不仅可以共享不可变对象,甚至也可以共享它们的内部信息。

不可变对象为其他对象提供了大量的构件(building blocks),无论是可变的还是不可变的对象。如果知道一个复杂对象的内部的组件对象是不会改变的,要维护它们的不变约束也是比较容易的。(也存在一些特例,如不可变对象是Map或Set,尽管这样破坏了不变性约束,但也不用担心它们的值会发生变化)

不可变类真正唯一的缺点是,需要为每一个不同值都创建独立的对象。创建这种对象的成本可能会很高,特别是在大型的对象的情况。

除了使用final让类无法被继承,还可以通过将所有的构造器变成私有的或包级私有化的,并添加公有的静态工厂方法来替代公有的构造器。

public class Complex1 {
    private final double re;
    private final double im;

    private Complex1 (double re,double im) {
        this.re=re;
        this.im=im;
    }
    public static Complex1 valueOf(double re,double im){
        return new Complex1(re,im);
    }
    public static Complex1 valueOfPolar(double r , double theta){
        return new Complex1(r*Math.cos(theta),r*Math.cos(theta));
    }
}
           

虽然这种方法并不常用,但它经常是最好的替代方法。它最灵活,因为它允许使用多个包级私有的实现类。

例如,需要提供一种“基于极坐标创建复数”的方式(Complex1中的valueOfPolar)。如果通过构造器来实现代码就会特别凌乱,如果通过静态工厂方法就不会出现难理解的问题了。

如果是不可信任类的实例,就必须假设它可能是可变的前提下对它进行保护性拷贝。

不可变类的规则是:没有方法会修改对象,并且它的所有域都必须是final的。但是,实际情况是为了提高性能会有所放松。实际要求是:没有一个方法能够对对象的状态产生外部可见的改变。所以许多不可变类会拥有一个或多个非final域,它们在第一次执行这些计算的时候,把开销昂贵的计算结果缓存起来,如果再次请求同样的计算,就可以直接返回结果,从而节约计算开销。因为对象是不可变的,它的不变性保证了这些计算,如果被再次执行会产生同样的结果。

没必要为每个get都编写相应的set方法,除非有很好的理由要让类成为可变的类,否则就应该是不可变的。应该让一些小的值对象,成为不可变的;也应该考虑让一些较大的值对象成为不可变的,只有当你确认有必要实现令人满意的性能时,才应该为不可变类提供公有的可变配套类。

如果类不能被设计成不可变的,仍然要限制它的可变性。除非有令人满意的理由要使域变成非final的,否则每个域都应该是final的。

构造器应该创建完全初始化的对象,并建立起所有的约束。不要在构造器或静态工厂方法之外提供公有的初始化方法。也不应该提供“重新初始化”方法。

继续阅读