不可变类是其实例不能被修改的类。 比如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.