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

如果你可以准确预测客户端要在你的不可变类上执行哪些复杂的操作,那么包级私有可变伙伴类的方式可以正常工作。如果不是的话,那么最好的办法就是提供一个公开的可变伙伴类。 这种方法在Java平台类库中的主要例子是String类,它的可变伙伴类是StringBuilder(及其过时的前身StringBuffer类)。

现在你已经知道如何创建一个不可改变类,并且了解不变性的优点和缺点,下面我们来讨论几个设计方案。 回想一下,为了保证不变性,一个类不得允许子类化。 这可以通过使类用 final 修饰,但是还有另外一个更灵活的选择。 而不是使不可变类设置为 final,可以使其所有的构造方法私有或包级私有,并添加公共静态工厂,而不是公共构造方法(条目 1)。 为了具体说明这种方法,下面以Complex为例,看看如何使用这种方法:

// Immutable class with static factories instead of constructors public class Complex {     private final double re;     private final double im;     private Complex(double re, double im) {         [this.re]() = re;         [this.im]() = im;     }     public static Complex valueOf(double re, double im) {         return new Complex(re, im);     }     ... // Remainder unchanged }

这种方法往往是最好的选择。 这是最灵活的,因为它允许使用多个包级私有实现类。 对于驻留在包之外的客户端,不可变类实际上是final的,因为不可能继承来自另一个包的类,并且缺少公共或受保护的构造方法。 除了允许多个实现类的灵活性以外,这种方法还可以通过改进静态工厂的对象缓存功能来调整后续版本中类的性能。

当BigInteger和BigDecimal被写入时,不可变类必须是有效的final,因此它们的所有方法都可能被重写。不幸的是,在保持向后兼容性的同时,这一事实无法纠正。如果你编写一个安全性取决于来自不受信任的客户端的BigInteger或BigDecimal参数的不变类时,则必须检查该参数是“真实的”BigInteger还是BigDecimal,而不应该是不受信任的子类的实例。如果是后者,则必须在假设可能是可变的情况下保护性拷贝(defensively copy)(条目 50):

public static BigInteger safeInstance(BigInteger val) {     return val.getClass() == BigInteger.class ?             val : new BigInteger(val.toByteArray()); }

在本条目开头关于不可变类的规则说明,没有方法可以修改对象,并且它的所有属性必须是final的。事实上,这些规则比实际需要的要强硬一些,其实可以有所放松来提高性能。 事实上,任何方法都不能在对象的状态中产生外部可见的变化。 然而,一些不可变类具有一个或多个非final属性,在第一次需要时将开销昂贵的计算结果缓存在这些属性中。 如果再次请求相同的值,则返回缓存的值,从而节省了重新计算的成本。 这个技巧的作用恰恰是因为对象是不可变的,这保证了如果重复的话,计算会得到相同的结果。

例如,PhoneNumber类的hashCode方法(第53页的条目 11)在第一次调用改方法时计算哈希码,并在再次调用时对其进行缓存。 这种延迟初始化(条目 83)的一个例子,String类也使用到了。

关于序列化应该加上一个警告。 如果你选择使您的不可变类实现Serializable接口,并且它包含一个或多个引用可变对象的属性,则必须提供显式的readObject或readResolve方法,或者使用ObjectOutputStream.writeUnshared和ObjectInputStream.readUnshared方法,即默认的序列化形式也是可以接受的。 否则攻击者可能会创建一个可变的类的实例。 这个主题会在条目 88中会详细介绍。

总而言之,坚决不要为每个属性编写一个get方法后再编写一个对应的set方法。 除非有充分的理由使类成为可变类,否则类应该是不可变的。 不可变类提供了许多优点,唯一的缺点是在某些情况下可能会出现性能问题。 你应该始终使用较小的值对象(如PhoneNumber和Complex),使其不可变。 (Java平台类库中有几个类,如java.util.Date和java.awt.Point,本应该是不可变的,但实际上并不是)。你应该认真考虑创建更大的值对象,例如String和BigInteger ,设成不可改变的。 只有当你确认有必要实现令人满意的性能(条目 67)时,才应该为不可改变类提供一个公开的可变伙伴类。

对于一些类来说,不变性是不切实际的。如果一个类不能设计为不可变类,那么也要尽可能地限制它的可变性。减少对象可以存在的状态数量,可以更容易地分析对象,以及降低出错的可能性。因此,除非有足够的理由把属性设置为非 final 的情况下,否则应该每个属性都设置为 final 的。把本条目的建议与条目15的建议结合起来,你自然的倾向就是:除非有充分的理由不这样做,否则应该把每个属性声明为私有final的

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

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