第十五条:使可变性最小
1.什么是不可变类?
(1)需要的所有参数必须在实例化的时候都传进去。
(2)对象中所有信息在对象的整个生命周期中都保持不变。
2.使类不可变的原则
(1)不要提供任何修改对象状态的方法。
(2)保证类不会被继承。
(3)使所有的域都是final类型的。
(4)使所有的域都是私有类型的。
(5)确保对于任何可变组件的互斥性。意思就是,确保在该类的外部不会获取到该类中可变对象的引用。比如下面这个例子:
public class MyObject{
private final List<String> list = new ArrayList<>;//可变对象
public List<String> getList() {
return new ArrayList(list);
}
public void setList(List<String> list) {
this.list = new ArrayList(list);
}
}
3.不可变类的优点
(1)不可变类简单。
不可变类只有一种状态,就是它被创建出来时候的状态,如果你要根据这个类进行一系列复杂操作,那么这个操作无论在什么时候结果都是相同的,所以你可以直接将结果缓存起来,在下一次执行同样操作的时候取出来,而不必再进行下一次操作。
(2)不可变类本质上是线程安全的,它不需要同步锁。
(3)对于不可变类,你永远都不需要实现拷贝方法。拷贝方法对它来说是没有意义的。
(4)不可变类可以被自由的共享。
4.不可变对象的缺点
(1)对于每一个不同的值都需要创建一个新的对象。
5.缺点的弥补办法
(1)先猜测一下经常用到哪些多步骤的操作,然后将它们作为基本数据类型提供。比如Integer,它将值为-128到127的对象缓存起来,但调用valueOf(int i)的时候,直接从缓存中拿,不用再重复创建提高效率。
(2)我们可以创建一个可变配套类。例如String是一个不可变类,它的可变配套类为StirngBuilder和StringBuffer。下面是一个例子,我们现在实现一个复数类,对外提供一个相加的方法如下:
public class Complex {
private final double re;//实部
private final double im;//虚部
private Complex(double re, double im) {
this.re = re;
this.im = im;
}
public static Complex valueOf(double re, double im) {
return new Complex(re, 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 class ComplexBuilder {
private double re;
private double im;
private ComplexBuider(double re, double im) {
this.re = re;
this.im = im;
}
public static ComplexBuider newInstance(Complex c) {
return new ComplexBuilder(c.realPart(), c.imaginaryPart());
}
public void add(Complex c) {
this.re = this.re + c.realPart();
this.im = this.im + c.imaginaryPart();
}
public Complex toComplex() {
return Complex.valueOf(this.re, this.im);
}
}
在客户端中我们如果需要用一个复数和另一个复数相加100次,我们如果不用ComplexBuilder的话就像下面这样,算上最开始穿件的两个实例,我们将会创建102个实例:
public class Test {
@Test
public void addNoBuiderTest() throws Exception{
Complex c1 = Complex.valueOf(1, 2);
Complex c2 = Complex.valueOf(2, 3);
for (int i = 0 ; i < 100 ; i++) {
c1 = c1.add(c2);
}
}
}
现在改用ComplexBuilder,现在我们只会创建4个实例:
public class Test {
@Test
public void addNoBuiderTest() throws Exception{
Complex c1 = Complex.valueOf(1, 2);
Complex c2 = Complex.valueOf(2, 3);
ComplexBuilder cb = ComplexBuider.newInstance(c1);
for (int i = 0 ; i < 100 ; i++) {
cb.add(c2);
}
c1 = cb.toComplex();
}
}
6.总结
(1)坚决不要为每个getter都生成setter。
(2)能将类做成不可变的就做成不可变的。
(3)一般比较小的值类都是需要做成不可变的。
(4)对于一些比较大的值类尽量考虑实现成不可变类。
(5)性能方面很有必要的时候才需提供配套类。
(6)如果真的不能作为不可变类,那就尽量限制其可变性
(7)对于一个类的初始化只能在构造器或是静态工厂中完成。也就是说类的初始化操作(赋值之类的)只能执行一次,就是在构造器或是静态工厂中。这一点参考java类库中的TimerTask类。