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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

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