1.不可变类的优势

不可变类只是其实例不能被修改的类。不可变的类比可变类更加易于设计、实现和使用。它们不容易出错,且更加安全。

Java 平台类库包含许多不可变的类,包括 String 类、基本类型包装类以及 BigInteger 类和 BigDecimal 类。 有很多很好的理由:不可变类比可变类更易于设计,实现和使用。 他们不容易出错,并且更安全

2.如何设计不可变类

1.不要提供任何会修改对象状态的方法。

2.保证类不会被扩展。

3.使所有的域都是 final 的。

4.使所有的域都是私有的。

5.确保对于任何可变组建的互斥访问。

不可变类实例:

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实例,而不是修改这个实例

3.优势

  • 1.不可变对象本质上是 线程安全 的,它们不要求同步
  • 2.不可变类可以 提供一些静态工厂 ,它们把频繁的请求的实例缓存起来,从而当现有实例可以符合请求的时候,就不必创建新的实例
  • 3.“不可变对象 可以被自由的共享” ==> 永远不需要进行保护性拷贝。
  • 4.不仅可以共享不可变对象,甚至也 可以共享它们的内部信息
public class BigInteger {
    public BigInteger negate() {
        return new BigInteger(this.mag, -this.signum);
    }
}

BigInteger 类内部用了符合数值表示法,符合用一个int类型的值来表示,数值则用int数组表示。negate方法产生一个新的BigInteger ,其中数值是一样的,符合是相反的。它并不需要拷贝数组;新建的BigInteger 也指向原始实例中的同一个内部数组。

  • 5.不可变对象 为其他对象提供了大量的构件,无论是可变的还是不可变的。

4.缺点

对于每一个不同的值都需要一个单独的对象

如果你执行一个多步骤的操作,并且每个步骤都会产生一个新的对象,除了最后的结果之外其他的对象最终都会被丢弃,此时性能问题就会显露出来。

5.如何优化

  • 1.使类是 final 的。
  • 2.让类的所有构造器都是私有的或者包级私有的并添加公有的静态工厂来代替公有构造器。
  • 3.坚决不要为每个 get 方法都提供一个 set 方法。
  • 4.构造器应该创建完全初始化的对象,不要在构造器或者静态工厂之外再提供公有的初始化方法,
  • 5.如果有的类不能做成不可变的,应该尽可能的限制它的可变性。
  • 6.如果不可变类要实现 Serilizable 接口,并且它包含一个或者多个指向可变对象的域,就必须提供一个显示的 readObject 或者 readResolve 方法。(或者使用 ObjectOutputStream.writeUnshared 和ObjectInputStream.readUnshared 方法)否则有可能通过序列号再反序列化会使不可变的类创建可变的实例。

示例

public class ComplexOther {
	private final double re;
	private final double im;
	
	private ComplexOther(double re,double im) {
		this.re = re;
		this.im = im;
	}
	
	public static ComplexOther getInstance(double re,double im) {
		return new ComplexOther(re,im);
	}
}

这种方式的好处:

  • 1.允许使用多个包级实现类
  • 2.可以提供缓存能力
  • 3.具有静态工厂的优势,可以清楚的表明它的功能。

6.结论

不可变类有很多好处,因此合适的适用场景下,可以考虑将类设计生不可变类,除非是真的有必要换成可变类。可变类也需要尽量将可变性设置为最小。;

7.参考文献

《Effective Java》
https://www.jianshu.com/p/2293d0e7a0d