不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期(lifetime)内固定不变。Java平台类库中包含许多不可变的类,其中有String、基本类型的包装类、BigInteger和BigDecimal。存在不可变的类有许多理由:不可变的类比可变的泪更加易于设计、实现和使用。他们不容易出错,且更加安全。
为了使类成为不可变,要遵循下面五条规则:
1、不要提供任何会修改对象状态的方法(也称为mutator),即改变对象属性的方法。
2、保证类不会被扩展。这样可以防止粗心或者恶意的子类假装对象的状态已经改变,从而破坏该类的不可变行为。为了防止子类化,一般做法是使这个类成为final的。
3、使所有的域都是final的。通过系统的强制方式,这可以清楚地表明你的意图。而且,如果一个指向新创建实例的引用在缺乏同步机制的情况下,从一个线程被传递到另一个 线程,就必须确保正确的行为。
4、使所有的域都成为私有的。这样可以防止客户端获得访问被域引用的可变对象的权限,并防止客户端直接修改这些对象。虽然从技术上讲,允许不可变的类具有公有的final 域,只要这些域包含基本类型的值或者指向不可变对象的引用,但是不建议这样做,因为这样会使得在以后的版本中无法再改变内部的表示法。
5、确保对于任何可变组件的互斥访问。如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。并且,永远不要用客户端提供的对象引用初 始化这样的域,也不要从任何访问方法(accessor)中返回该对象引用。在构造器、访问方法和readObject中请使用保护性拷贝(defensive copy)技术。
一个复数例子:
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 * hashDouble(re);
result = 31 * result + hashDouble(im);
return result;
}
private int hashDouble(double val) {
long longBits = Double.doubleToLongBits(val);
return (int)(longBits ^ (longBits >>> 32));
}
@Override
public String toString() {
char s = '-';
return "(" + re + " + " + im + "i)";
}
}
加法、减法、乘法和除法四种基本的算术运算返回的是一个新的Complex实例,而不是修改这个实例。这样实例的状态就无法被改变(状态在实例创建的时候就确定了,并且没有提供改变状态的方法),Complex类成为一个不可变类。
不可变类要比可变类更加易于设计,实现和使用。它们不容易出错,且更加安全。
不可变类有很多优点:、
1.不可变对象比较简单。
不可变对象可以只有一种状态,即被创建时的状态。
2.不可变对象本质上是线程安全的,它们不要去同步。当多个线程并发访问这样的对象时,它们不会遭到破坏。
客户端应该尽可能地重用现有的实例。例如Complex类可能提供下面的常量:
public static final Complex ZERO = new Complex(0, 0);
进一步扩展,不可变类可以提供一些静态工厂,把频繁被请求的实例缓存起来,从而当现有实例可以符合请求时,不必创建新的实例:
private static final Complex ZERO = new Complex(0, 0);
public Complex valueOfZero() {
return ZERO;
}
3.不可变对象可以被自由地共享。
4.不仅可以共享不可变对象,甚至也可以共享它们的内部信息。
如BigInteger类的内部使用了符号数值表示法。符号用一个int类型的值signum表示,数值则用一个int数组mag表示。它有一个negate方法产生一个新的BigInteger,其中数值是一样的,符号是相反的,它不需要拷贝数组,新建的BigInteger也指向原始实例中的同一个内部数组。
public BigInteger negate() {
return new BigInteger(this.mag, -this.signum);//构造器传入的参数是原始实例的数组this.mag
}
5.不可变对象为其他对象提供了大量的构件
不可变类真正唯一的缺点是,对于每个不同的值都需要一个单独的对象。考虑String的“+”,String s = “a”+“b”会创建三个字符串“a”、“b”、“ab”。创建这种对象的代价可能很高,特别是对于大型对象的情形。
为了确保不可变性,类绝对不允许自身被子类化。除了“使类成为final的”这种方法之外,还有另一种更加灵活的办法可以做到这一点。让不可变的类变成final的另一种办法就是,让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂来代替公有的构造器。如下:
public class Complex{
private final double re;
private final double im;
private Complex(double im, double re) {
this.im = im;
this.re = re;
}
public static Complex valueOf(double re,double im){
return new Complex(re,im);
}
}
总之,坚决不要为每个get方法编写一个相应的set方法。除非有很好的理由也要让类成为可变的类,否则就应该是不可变的。
对于有些类而言,其不可变性是不切实际的。如果类不能被做成是不可变的,仍然应该是尽可能地限制它的可变性。降低对象可以存在的状态数,可以更容易地分析该对象的行为,同时降低出错的可能性。因此,除非有令人信服的理由要使域变成是非final的,否则要使每个域都是final的。