不可变类应该是最终的?

时间:2008-09-28 17:32:58

标签: java oop final

它在this article中说:

  

让一个类成为最终因为它是不可变的是一个很好的理由。

我对此感到有点困惑......我理解不变性是线程安全性和简单性的POV中的好东西,但似乎这些问题与可扩展性有些正交。那么,为什么不变性是让一个班级最终成为一个很好的理由呢?

6 个答案:

答案 0 :(得分:11)

“Effective Java”一书中给出了对此的解释

考虑Java中的BigDecimalBigInteger类。

人们普遍不了解不可变类必须是最终的 当BigIntegerBigDecimal被写入时,他们的所有方法都可以 覆盖。不幸的是,在保留向后兼容性的同时,这无法得到纠正。

如果编写一个类,其安全性取决于来自不受信任的客户端的BigInteger或BigDecimal参数的不变性,则必须检查该参数是否为“真实”BigInteger或BigDecimal,而不是不受信任的子类的实例。如果是后者,则必须在假设它可能是可变的情况下进行防御性复制。

   public static BigInteger safeInstance(BigInteger val) {

   if (val.getClass() != BigInteger.class)

    return new BigInteger(val.toByteArray());


       return val;

   }

如果允许子类,则可能会破坏不可变对象的“纯度”。

答案 1 :(得分:10)

根据Liskov替换原则,子类可以延伸但从不重新定义其父类的合同。如果基类是不可变的,那么很难找到其功能可以在不违反合同的情况下有效扩展的示例。

请注意,原则上可以扩展不可变类并更改基本字段,例如如果基类包含对数组的引用,则数组中的元素不能声明为final。显然,方法的语义也可以通过覆盖来改变。

我想你可以将所有字段声明为私有,并将所有方法声明为final,但那么继承的用途是什么?

答案 2 :(得分:9)

因为如果这个类是最终的,你就无法扩展它并使它变得可变。

即使您将字段设为最终字段,这只表示您无法重新分配引用,但这并不意味着您无法更改引用的对象。

我没有看到设计中有很多用于不可变类的,也应该扩展,所以final有助于保持不变性。

答案 3 :(得分:6)

我认为主要是安全。出于同样的原因,String是final,任何与安全相关的代码都希望将其视为不可变的东西必须是最终的。

假设您有一个定义为不可变的类,请将其命名为MyUrlClass,但不要将其标记为final。

现在,有人可能会像这样编写安全管理器代码;

void checkUrl(MyUrlClass testurl) throws SecurityException {
    if (illegalDomains.contains(testurl.getDomain())) throw new SecurityException();
}

以下是他们在DoRequest(MyUrlClass网址)方法中的内容:

securitymanager.checkUrl(urltoconnect);
Socket sckt = opensocket(urltoconnect);
sendrequest(sckt);
getresponse(sckt);

但他们不能这样做,因为你没有让MyUrlClass最终成功。他们不能这样做的原因是,如果他们这样做,代码可以简单地通过覆盖getDomain()在第一次调用时返回“www.google.com”来避免安全管理器限制,并且“www.evilhackers.org”第二个,并将其类的对象传递给DoRequest()。

顺便说一下,如果它存在的话,我没有反对evilhackers.org ......

在没有安全问题的情况下,一切都是为了避免编程错误,当然由你来决定如何做到这一点。子类必须保持父母的合同,不变性只是合同的一部分。但是如果一个类的实例应该是不可变的,那么将它作为final是确保它们确实都是不可变的一种好方法(即没有可变的子类实例,它们可以在父类的任何地方使用调用了类。)

我不认为你引用的文章应该被视为“所有不可变类必须是最终的”的指令,特别是如果你有充分的理由设计你的不可变类继承。它所说的是保护不变性是最终的一个正当理由,其中虚构的性能问题(这是它在那时真正谈论的)是无效的。请注意,它给出了“一个不是为继承而设计的复杂类”作为同样有效的理由。可以肯定的是,在复杂类中没有考虑继承是一件可以避免的事情,就像在不可变类中没有考虑继承一样。但如果你不能解释它,你至少可以通过阻止它来表明这一事实。

答案 4 :(得分:0)

由于性能原因,使类成为不可变是一个好主意。以Integer.valueOf为例。当您调用此静态方法时,它不必返回新的Integer实例。它可以返回一个先前创建的实例安全,因为它知道上次当你没有修改它时它传递给你那个实例的引用(我想这也是一个很好的推理,从安全理由的角度来看)。

我同意Effective Java在这些问题上的立场 - 你应该设计你的类的可扩展性或使它们不可扩展。如果你打算做一些可扩展的东西,也许可以考虑一个接口或抽象类。

此外,您不必将课程定稿。您可以将构造函数设为私有。

答案 5 :(得分:0)

那么,为什么不变性成为最终上课的理由呢?

oracle docs中所述,基本上有4个步骤使类不可变。

所以其中一个点是

创建类Immutable类应标记为final或具有私有构造函数

以下是使类不可变的4个步骤(直接来自oracle文档)

  1. 不要提供" setter"方法 - 修改字段引用的字段或对象的方法。

  2. 将所有字段设为最终字段并保密。

  3. 不允许子类覆盖方法。最简单的方法是将类声明为final。更复杂的方法是使构造函数私有,并在工厂方法中构造实例。

  4. 如果实例字段包含对可变对象的引用,则不允许更改这些对象:

    • 不要提供修改可变对象的方法。
    • 不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始文件。