创建不可变类的最佳设计方法

时间:2015-04-06 11:47:50

标签: java immutability final

我正在阅读在Effective Java中创建不可变类时需要遵循的具体指导原则。

我读到In Immutable类方法不应该被覆盖,否则重写方法可能会改变方法的行为。以下是java中可用于解决此问题的设计方法: -

  1. 我们可以标记课程最终,但根据我的理解,它有一个缺点,它使课程不可扩展。

  2. 其次是使个别方法最终,但除了我们需要将每个方法单独标记为最终方法以防止覆盖之外,我不能得到其他缺点。

  3. 按照书中的说法,更好的方法是使构造函数为private或package-private,并提供用于创建对象的公共静态工厂方法。

  4. 我的问题是:即使我们在类中包含私有或默认构造函数,它也不能在同一个包中扩展(在package-private构造函数的情况下在其他包中),它有同样的问题,第一个有。它是如何被认为是比以前更好的方法?

2 个答案:

答案 0 :(得分:3)

不可变对象不应该是可扩展的。为什么呢?

因为扩展它将允许直接访问字段(如果它们是protected,这将允许编写改变它们的方法),或者添加可能是可变的状态。

想象一下,我们编写了一个扩展FlexiblyRoundableDouble的{​​{1}}类,它有一个额外的字段Double,可以让我们选择"舍入模式"。你可以为这个字段写一个setter,现在你的对象是可变的。

您可以争辩说,如果将所有方法都设置为final,则无法更改对象的原始行为。如果您将对象分配给roundingMode变量,那么可以访问roundingMode字段的唯一方法是新方法,这些方法在多态方面不可用。但是当一个班级的合同说它是不可变的时,你就会根据这个做出决定。例如,如果您为具有Double字段的类编写clone()方法或复制构造函数,则表示您不需要深层复制Double字段,因为它们不会改变它们的状态,因此可以在两个克隆之间安全地共享。

此外,您可以编写返回内部对象的方法,而不必担心调用者会更改该对象。如果对象是可变的,那么你必须制作一个防御性副本"它的。但如果它是不可变的,那么返回对实际内部对象的引用是安全的。

但是,如果有人将Double分配到您的FlexiblyRoundableDouble个字段之一,会发生什么?那个对象是可变的。 Double假定它不是,它将在两个对象之间共享,甚至可能由方法返回。然后调用者可以将其强制转换为clone(),更改字段......它将影响使用同一实例的其他对象。

因此,不可变对象应该是最终的。


所有这些与构造函数问题无关。对象可以使用公共构造函数安全地不可变(如FlexiblyRoundableDoubleStringDouble和其他标准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对于不可变类来说都是疯狂的。