Effective Java 第三版——17. 最小化可变性

Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。
在这里第一时间翻译成中文版。供大家学习分享之用。

Effective Java, Third Edition

17. 最小化可变性

不可变类简单来说是它的实例不能被修改的类。 包含在每个实例中的所有信息在对象的生命周期中是固定的,因此不会观察到任何变化。 Java平台类库包含许多不可变的类,包括String类,基本类型包装类以及BigInteger类和BigDecimal类。 有很多很好的理由:不可变类比可变类更容易设计,实现和使用。 他们不太容易出错,更安全。

要使一个类不可变,请遵循以下五条规则:

不要提供修改对象状态的方法(也称为mutators)。

确保这个类不能被继承。 这可以防止粗心的或恶意的子类,假设对象的状态已经改变,从而破坏类的不可变行为。 防止子类化通常是通过final修饰类,但是我们稍后将讨论另一种方法。

把所有属性设置为final。通过系统强制执行,清楚地表达了你的意图。 另外,如果一个新创建的实例的引用从一个线程传递到另一个线程而没有同步,就必须保证正确的行为,正如内存模型[JLS,17.5; Goetz06,16]所述。

把所有的属性设置为private。 这可以防止客户端获得对属性引用的可变对象的访问权限并直接修改这些对象。 虽然技术上允许不可变类具有包含基本类型数值的公共final属性或对不可变对象的引用,但不建议这样做,因为它不允许在以后的版本中更改内部表示(项目15和16)。

确保对任何可变组件的互斥访问。 如果你的类有任何引用可变对象的属性,请确保该类的客户端无法获得对这些对象的引用。 切勿将这样的属性初始化为客户端提供的对象引用,或从访问方法返回属性。 在构造方法,访问方法和readObject方法(条目 88)中进行防御性拷贝(条目 50)。

以前条目中的许多示例类都是不可变的。 其中这样的类是条目 11中的PhoneNumber类,它具有每个属性的访问方法(accessors),但没有相应的设值方法(mutators)。 这是一个稍微复杂一点的例子:

// Immutable complex number class 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 plus(Complex c) {         return new Complex(re + c.re, im + c.im);     }     public Complex minus(Complex c) {         return new Complex(re - c.re, im - c.im);     }     public Complex times(Complex c) {         return new Complex(re * c.re - im * c.im,                 re * c.im + im * c.re);     }     public Complex dividedBy(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 47 to find out why we use compare instead of ==         return Double.compare(c.re, re) == 0                 && Double.compare(c.im, im) == 0;     }     @Override     public int hashCode() {         return 31 * Double.hashCode(re) + Double.hashCode(im);     }     @Override     public String toString() {         return "(" + re + " + " + im + "i)";     } }

这个类代表了一个复数(包含实部和虚部的数字)。 除了标准的Object方法之外,它还为实部和虚部提供访问方法,并提供四个基本的算术运算:加法,减法,乘法和除法。 注意算术运算如何创建并返回一个新的Complex实例,而不是修改这个实例。 这种模式被称为函数式方法,因为方法返回将操作数应用于函数的结果,而不修改它们。 与其对应的过程(procedural)或命令(imperative)的方法相对比,在这种方法中,将一个过程作用在操作数上,导致其状态改变。 请注意,方法名称是介词(如plus)而不是动词(如add)。 这强调了方法不会改变对象的值的事实。 BigInteger和BigDecimal类没有遵守这个命名约定,并导致许多使用错误。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyypjx.html