不是“把内存当做一个巨大的数组来看待”,Java不会破坏内存。
编写面对客户的不良行为时仍能保持健壮性的类,是非常值得投入时间的。
不可变类的构造函数
上面的类不是不可变类,因为Date类本身是可变的。
p对象的end对象已经被改变了。
不可变类的构造函数如何实现
对于构造函数的每个可变参数进行保护性拷贝(defensive copy),并且使用备份对象作为Period实例的组件,而不使用原始的对象。
保护性拷贝是在检查参数的有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是针对原始的对象。
这样做可以避免在“危险阶段(从检查参数开始,直到拷贝参数之间的时间段)”期间从另外一个线程改变类的参数。
这里我们没有使用Date的clone方法来进行保护性拷贝。因为Date是非final的,不能保证clone方法一定返回java.util.Date的对象:它有可能返回专门出于恶意的目的而设计的不可信的子类的实例。——为了阻止这种攻击,对于参数类型可以被不可信任方子类化的参数,请不要使用clone方法进行保护性拷贝。
访问方法提供了对其可变内部成员的访问能力
访问方法提供了对其可变内部成员的访问能力——访问方法返回可变内部域的保护性拷贝
访问方法与构造函数不同,它们在进行保护性拷贝的时候允许使用clone方法。因为Period内部的Date对象的类是java.util.Date,而不可能是其他某个潜在的不可信子类。
使用的场景
1.不可变类
2.如果方法或构造函数允许客户提供的对象进入到内部数据结构中,需要考虑客户提供的对象是否是可变的,并且能否容忍对象进入数据结构之后发生的变化。
如果不允许,则应该对该对象进行保护性拷贝,并且让拷贝之后的对象而不是原始对象进入到数据结构中。
如果你正在考虑使用由客户提供的对象引用作为内部Set实例的元素,或者作为内部Map实例的键,就应该意识到,如果这个对象在插入之后再被修改,Set或者Map的约束条件就会遭到破坏。
3.在把一个指向内部可变组件的引用返回给客户端之前
长度非零的数组总是可变的。
在把内部数组返回给客户端之前,应该总要进行保护性拷贝,或给客户端返回该数组的不可变视图(immutable view)。
启示
只要有可能,都应该使用不可变的对象作为对象内部的组件。
建议使用long基本类型作为内部的时间表示法,而不是使用Date对象引用。
如果具有从客户端得到或者返回到客户端的可变组件,类就必须保护性地拷贝这些组件。
如果拷贝的成本收到限制,并且类信任它的客户端不会不恰当地修改组件,就可以在文档中指明客户端的职责是不得修改收到影响的组件,以此来代替保护性拷贝。