不可变类是其实例不能被修改的类。
比如String
、基本数据类型的包装类(String的基本数据类型就是String)、BigInteger
、BigDecimal
。
要使一个类变成不可变的,要遵守下面的规则:
不提供任何修改对象状态的方法。比如setter getter中的setter(也称作mutator)就不能提供。
保证类不被扩展。通常用finla class实现(也可以用private构造函数的方法,也就是静态工厂方法里提到的)。
所有的fileds都设成私有的、final的。
4、 Ensure exclusive access(互斥访问) to any mutable components.
下面贴一段很长的代码,是一个复数运算的类:
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
// 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;
// See page 43 to find out why we use compare instead of ==
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)";
}
}
可以看到这个类符合了上面列出来的要求。有个特点,每次计算完成之后都new
一个实例,而不是修改传进来的。不可变类可以只有一种状态,就是创建时的状态。
不可变类天生就是线程安全的,不要求同步。多个线程并发访问的时候不会遭到破坏。所以可以被自由地共享。所以你根本不需要给不可变类提供拷贝构造器。
坚决不要为每个getter都配置一个setter。除非有很好的理由让类变成可变的,不然他就应该是不可变的。
缺点
不可变类唯一的缺点是,对于每一个类不同的值都需要一个但对的对象。比如要创建几百万个BigInteger
。
BONUS:
我一直疑惑为什么String可以用=来初始化,而不用new;后来想了想,因为String是符合类型呀,它的基本数据类型和包装数据类型都是String。
引用:
String password="ok";利用到了字符串缓冲池,也就是说如果缓冲池中已经存在了相同的字符串,就不会产生新的对象,而直接返回缓冲池中的字符串对象的引用。
如:
String a = "ok";
String b = "ok";
String c = new String("ok");
String d = new String("ok");
System.out.println(a==b);//将输出"true";因为两个变量指向同一个对象。
System.out.println(c==d);//将输出"flase";因为两个变量不指向同一个对象。虽然值相同,只有用c.equals(d)才能返回true.