不可变类是指:其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。
如何使类成为不可变类,要遵循下面五条规则:
- 不要提供任何会修改对象状态的方法。
- 保证类不会被扩展。为了防止子类化,一般做法是使这个类成为final的 。
- 使所有的域都是final的。一是为了表明意图,二是为了在多线程间确保对对象使用正确的行为。
- 使所有的域都成为私有的。这样可以防止客户端获得访问被域引用的可变对象的权限,并防止客户端直接修改这些对象。
5.确保对于任何可变组件的互斥访问。如果类具有指向可变对象的与u,则必须确保该类的客户端无法获得指向这些对象的引用。并且,永远不要用客户端提供的对象引用来初始化这样的域,也不要从任何方法中返回该对象引用。
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 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;
result = 31 * result + hashDouble(re);
result = 31 * result + hashDouble(im);
return result;
}
private int hashDouble (double val) {
long longBits = Double.doubleToLongBits(re);
return (int) (longBits ^ (longBits >>> 32));
}
}
这个类表示一个复数(具有实部和虚部)。除了标准的Object方法之外,它还提供了针对实部和虚部的访问方法,以及4种基本的算术运算:加法,减法,乘法和除法。注意这些算术运算是创建并返回新的Complex实例,而不是修改这个实例。
不可变类要比可变类更加易于设计,实现和使用。它们不容易出错,且更加安全。
不可变类有很多优点:、
1.不可变对象比较简单。
不可变对象可以只有一种状态,即被创建时的状态。
2.不可变对象本质上是线程安全的,它们不要去同步。
当多个线程并发访问这样的对象时,它们不会遭到破坏。
3.不可变对象可以被自由地共享。
最简单的例子就是公有的静态final常量。例如:
4.不仅可以共享不可变对象,甚至也可以共享它们的内部信息。例如BigInteger类内部使用了符号数值表示法。符号用一个int类型的值来表示,数值则用一个int数组表示。negate方法产生一个新的BigInteger,其中数值是一样的,符号则是相反的。它并不需要拷贝数组,新建的BigInteger也指向原始实例中的同一个内部数组。
5.不可变对象为其他对象提供了大量的构件
当然,不可变类也有它的缺点:对于每个不同的值都需要一个单独的对象。
如果你执行一个多步骤的操作,并且每个步骤都会产生一个新的对象,除了最后的结果之外其他的对象最终都会被丢弃,此时性能问题就会显露出来。
处理这种问题的办法:提供可变配套类
如BigInteger提供了一个包级私有的可变配套类“MutableBigInteger”
“MutableBigInteger是BigInteger类的另一个版本,它的特点是不创建临时对象的前提上使调用程序得到象BigInteger类型的返回值(称为可变对象技术)。因为大整数的除法是由大量的其他算术操作组成的,所以需要大量的临时对象,而完成大量的操作而不创建新的对象可以极大地改善程序的性能,(因为创建对象的代价是很高的)所以在Java的大整数类中使用MutableBigInteger类中的方法来执行大整数除法。”
还有例如String类,它的可变配套类是StringBuilder和StringBuffer
让类成为不可变类除了上面的方法,还有一种办法就是:让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂来代替公有的构造器。
它相比之前的方法:
- 更灵活,允许使用多个包级私有的实现类。
- 还可能通过改善静态工厂的对象缓存能力,来改进该类的性能。
- 静态工厂的名字可以清楚的表明它的功能。
告诫:
如果你的不可变类实现了Serializable接口,并且它包含一个或多个可变对象的域,就必须提供一个显式的readObject或者readResolve方法,否则有可能通过序列号再反序列化会使不可变的类创建可变的实例。
如果类不能被做成是不可变的,仍然应该尽可能地限制它的可变性。
构造器应该创建完全初始化的对象,并建立起所有的约束关系。不要在构造器或者静态工厂之外再提供公有的初始化方法,也不要提供重新初始化方法。
总结:对于每个类,都需要去仔细考虑一下它的可变性,努力使类的可变性最小化,这样会节省很多后期排错维护的成本,类的变化也更容易掌控在自己手里。