不可变类是,其实例不能修改的类。每个实例所包含的信息都必须在创建该实例的时候提供,并在对象的整个生命周期内固定不变。
使类变成不可变类应该遵循的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的。
构造器应该创建完全初始化的对象,并建立起所有的约束。不要在构造器或静态工厂方法之外提供公有的初始化方法。也不应该提供“重新初始化”方法。