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的约束关系。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,955评论 18 399
  • 目录 第二章 创建和销毁对象 1 考虑用静态工厂方法替代构造器 对于代码来说, 清晰和简洁是最重要的. 代码应该被...
    高广超阅读 5,295评论 0 12
  • 对象的创建与销毁 Item 1: 使用static工厂方法,而不是构造函数创建对象:仅仅是创建对象的方法,并非Fa...
    孙小磊阅读 6,225评论 0 3
  • 《梦痕》 著/顾城 灯 淡黄的眼睫 不再闪动 黑暗在淤集 无边无际 掩盖了—— 珊瑚般生长的城市 和漠漠沉淀的历史...
    梦呓ricot阅读 4,097评论 2 9
  • 只要你玩儿微信,我相信,你一定被“拉票”过。 如今的各种投票真的是五花八门,多如牛毛,让人眼花缭乱,防不胜防。 那...
    西瓜甜甜啦阅读 6,813评论 10 21