Chapter4 Item15 Minimize mutability

不可变类是指其实例不可被修改的类。实例中的所有信息都是在创建实例时提供的,并且在对象生命周期内保持不变。JDK中有许多这种不可变类,例如String、基本类型包装类、BigInteger、BigDecimal。
不可变类有许多优点:易于设计、易于实现、易于使用。它们更不容易出错,并且更安全。

如何编写不可变类

  1. 不提供任何修改类状态的方法(mutator);
  2. 保证类不能被扩展。这可以防止粗心或者恶意的子类破坏不可变行为。可以将类声明为final,后面我们也会讨论其他方法;
  3. 使所有域都是final的。通过系统强制的方式明确地表明你的意图。Also, it is necessary to ensure correct behavior if a reference to a newly created instance is passed from one thread to another without
    synchronization, as spelled out in the memory model [JLS, 17.5; Goetz06 16].
  4. 使所有域都是private的。这可以防止可变类引用这些域,并直接修改。虽然从技术上讲允许不可变类具有public final域,只要这些域指向基本类型或者不可变对象,但是不建议这样做,因为这样会导致以后无法改变内部表示(Item13)。
    5.** 确保对任何可变组件的访问都是互斥的**。
    如果类中有指向可变对象的域,那就需要确保客户端不能获得这些可变对象的引用。
    永远不要将这种域初始化为客户端提供的对象引用。
    不要通过accessor返回这种对象引用。
    在构造函数、accessor、readObject方法(Item76)中使用保护性拷贝(Item39)。

范例:

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 == //因为有Double.NaN
  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)";
}

分别为实部和虚部提供了accessor,还提供了4中基本算术运算:加减乘除。注意这些算术运算创建并返回一个新的Complex实例,而不是修改当前实例。大所数重要的不可变类都是用了这种模式。这被称为函数方式(functional approach),因为这些方法返回对操作数的运算结果,但是并不修改这些操作数。与之对应的则是更常见的过程方式(procedural approach),或称为命令方式(imperative approach),会改变操作数状态。

优点

简单

不可变对象只有一种状态,就是创建时的状态

线程安全,可自由地共享

为了满足item5,避免创建过多的对象,可以利用cache方法和item1, 将经常被请求的实例缓存起来,当现有实例满足请求的时候,就不必去创建新的实例。

   public static final BigInteger ZERO = new BigInteger(new int[0], 0);
    public static final BigInteger ONE = valueOf(1);
    public static final BigInteger TEN = valueOf(10);

eg. 所有的基本类型包装类、BigInteger都是这样做的。使用静态工厂使得客户端共享实例而不是创建新实例,可以降低内存占用和垃圾收集成本。设计新类时选择用静态工厂而不是公共构造方法,可以让你以后灵活地添加缓存,而不必修改客户端

无需保护性拷贝(Item39),无需提供拷贝构造方法

你不需要,也不应该为不可变类提供clone方法或者拷贝构造函数(Item11)。【反例】这一点在Java平台早期并没有被很好地理解,导致String类具有拷贝构造函数,应该尽量不去用这个函数(Item5)。

可以共享内部信息

不仅可以共享不可变对象,还可以共享他们的内部信息。。【例】BigInteger类内部使用了一个符号数值表示法(sign-magnitude representation),符号用一个int表示,数值则用一个int数组表示。negate()方法会创建一个数值相同但符号相反的新BigInteger,该方法不需要拷贝数组,新创建的BigInteger只需要指向源对象中的数组即可。

/**
     * Returns a BigInteger whose value is {@code (-this)}.
     *
     * @return {@code -this}
     */
    public BigInteger negate() {
          return new BigInteger(this.mag, -this.signum);
    }

为其他对象提供构件

【例】不可变对象构成了大量的map key和set元素,一旦不可变对象进入map或set中,你就不必担心他们的值变化导致破坏map和set的约束关系。

你可能感兴趣的:(Chapter4 Item15 Minimize mutability)