Immutable(多线程学习系列 - 2 - Immutable Pattern的补充)
在多线程学习系列 - 2 - Immutable Pattern 中,作者告诉我们不可变类的好处以及如何设计不可变类
下面的内容是Effective Java(v2)中的第15条--使4可变性最小化
作者给出了设计不可变类的一些建议,多线程学习系列 - 2 - Immutable Pattern 中一些建议过于强硬,比如类要是final,域要是final等等
1.不要提供任何会修改对象状态的方法
2.保证类不会被扩展(final类)
可以防止粗心或恶意的子类假装对象的状态已经改变,从而破坏该类的不可变行为
3.使所有的域都是final的
4.使所有的域都是私有的
防止客户端直接修改这些对象
5.确保对于任何可变组件的互斥访问
如果类具有指向可变对象的域,则必须确保该类的客户端无法获得这些对象的引用
永远不要用客户端提供的对象引用来初始化这样的域
也不要从任何访问方法中返回该对象引用
在构造器,访问方法和readObject方法中使用保护性拷贝
effective java中给了一个例子
Complex为复数,具有实部和虚部
public final class Complex { private final double re; private final double im; public Complex(double re, double im) { this.re = re; this.im = im; } 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); // Accessors with no corresponding mutators public double realPart() { return re; } public double imaginaryPart() { return im; } 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 o) { if (o == this) return true; if (!(o instanceof Complex)) return false; Complex c = (Complex) o; 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)"; } }
下面分析下复数这个类
它是final的,成员变量均为private final
除了构造函数,没有任何能够改变自己状态的方法
构造函数的参数为double,也是不可变的
有几个方法是返回Complex,注意这些方法,他们无一不是new一个新Complex,并没有修改自身
所以Complex是不可变的
不可变对象可以*的被共享
我们应该尽量的重用现有实例,所以对于频繁使用的值应该提供public 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);
我们还可以进一步扩展一下想法,给它提供一个静态工厂方法 ,这样可以更灵活的控制缓存,自己选择是否创建新的实例
静态工厂有许多优势,举例来说:假如你想以极坐标的方式创建一个复数,那么构造函数可能会是下面这样
public Complex(double re, double im){...}
可是已经有了一个相同签名的构造函数了,所以这样是不行的
而使用静态工厂,你就可以更改签名了,例如下面这样
public static Complex valueOfPolar(double r, double theta) { return new Complex(r * Math.cos(theta), r * Math.sin(theta)); }
不可变对象无须任何拷贝,所以也不用提供clone或者拷贝构造器
ps:听说这点在java平台的早期不好理解,所以String还保留了拷贝构造器
不可变对象还可以共用内部信息
BigInteger中的一个例子
在它的negate方法中新建另一个对象,数值是一样的,符号是相反的,并没有拷贝数组
final int[] mag; public BigInteger negate() { return new BigInteger(this.mag, -this.signum); }
对于第二条保证类不会被扩展(final类) ,可以不用那么严格,不一定非要是final类才可以
我们把所有的构造函数设置为private,通过静态工厂创建实例,那么也一样可以达到final类的效果
添加如下方法即可
public static Complex valueOf(double re, double im) { return new Complex(re, im); }
对于第三条使所有的域都是final的 ,也可以不用那么严格
只要没有一个方法能够对对像的状态产生外部可见的改变就可以
这些非final域在第一次被请求执行计算这些计算结果的时候把一些开销昂贵的计算结果缓存在这些域中达到复用,因为对象是不可变的,所以每次请求返回的结果一致
转贴请保留以下链接
本人blog地址