我正在阅读在Effective Java中创建不可变类时需要遵循的具体指导原则。
我读到In Immutable类方法不应该被覆盖,否则重写方法可能会改变方法的行为。以下是java中可用于解决此问题的设计方法: -
我们可以标记课程最终,但根据我的理解,它有一个缺点,它使课程不可扩展。
其次是使个别方法最终,但除了我们需要将每个方法单独标记为最终方法以防止覆盖之外,我不能得到其他缺点。
按照书中的说法,更好的方法是使构造函数为private或package-private,并提供用于创建对象的公共静态工厂方法。
我的问题是:即使我们在类中包含私有或默认构造函数,它也不能在同一个包中扩展(在package-private构造函数的情况下在其他包中),它有同样的问题,第一个有。它是如何被认为是比以前更好的方法?
答案 0 :(得分:3)
不可变对象不应该是可扩展的。为什么呢?
因为扩展它将允许直接访问字段(如果它们是protected
,这将允许编写改变它们的方法),或者添加可能是可变的状态。
想象一下,我们编写了一个扩展FlexiblyRoundableDouble
的{{1}}类,它有一个额外的字段Double
,可以让我们选择"舍入模式"。你可以为这个字段写一个setter,现在你的对象是可变的。
您可以争辩说,如果将所有方法都设置为final,则无法更改对象的原始行为。如果您将对象分配给roundingMode
变量,那么可以访问roundingMode
字段的唯一方法是新方法,这些方法在多态方面不可用。但是当一个班级的合同说它是不可变的时,你就会根据这个做出决定。例如,如果您为具有Double
字段的类编写clone()
方法或复制构造函数,则表示您不需要深层复制Double
字段,因为它们不会改变它们的状态,因此可以在两个克隆之间安全地共享。
此外,您可以编写返回内部对象的方法,而不必担心调用者会更改该对象。如果对象是可变的,那么你必须制作一个防御性副本"它的。但如果它是不可变的,那么返回对实际内部对象的引用是安全的。
但是,如果有人将Double
分配到您的FlexiblyRoundableDouble
个字段之一,会发生什么?那个对象是可变的。 Double
假定它不是,它将在两个对象之间共享,甚至可能由方法返回。然后调用者可以将其强制转换为clone()
,更改字段......它将影响使用同一实例的其他对象。
因此,不可变对象应该是最终的。
所有这些与构造函数问题无关。对象可以使用公共构造函数安全地不可变(如FlexiblyRoundableDouble
,String
,Double
和其他标准Java不可变项所示。静态工厂方法只是利用这一事实,即对象是不可变的,而其他几个对象可以安全地保存对它的引用,以创建具有相同值的更少对象。
答案 1 :(得分:2)
提供静态工厂方法为您提供了实施Flyweight模式的空间。
他们声明你应该隐藏使用构造函数创建新对象的可能性,而应该调用一个方法来检查"对象池中是否存在具有相似状态的对象& #34; (填充了等待重复使用的对象的地图)。不重用不可变对象是浪费内存;这就是鼓励String
文字的原因,并且new String()
被避开(除非需要)。
class ImmutableType {
private static final Map<Definition, ImmutableType> POOL = new HashMap<>();
private final Definition definition;
private ImmutableType(Definition def) {
definition = def;
}
public static ImmutableType get(Definition def) {
if(POOL.contains(def))
return POOL.get(def);
else {
ImmutableType obj = new ImmutableType(def);
POOL.put(def, obj);
return obj;
}
}
}
Definition
存储ImmutableType
的状态。如果池中已存在具有相同定义的类型,则重新使用它。否则,创建它,将其添加到池中,然后将其作为值返回。
关于标记类final
的语句,不可变类型首先不应该是可扩展的(以避免可能修改行为)。标记每个方法final
对于不可变类来说都是疯狂的。