21

它在这篇文章中说:

因为它是不可变的,所以将其设为 final 是一个很好的理由。

我对此感到有些困惑......我知道从线程安全和简单的 POV 来看,不变性是一件好事,但似乎这些问题与可扩展性有些正交。那么,为什么不可变性是使类成为 final 的一个很好的理由呢?

4

6 回答 6

11

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

考虑Java 中的BigDecimalBigInteger类。

BigInteger不可变类在编写时必须是有效的 final并没有被广泛理解BigDecimal,因此它们的所有方法都可能被覆盖。不幸的是,在保持向后兼容性的同时,这在事后无法纠正。

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

   public static BigInteger safeInstance(BigInteger val) {

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

    return new BigInteger(val.toByteArray());


       return val;

   }

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

于 2012-09-26T11:27:37.427 回答
10

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

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

我想您可以将所有字段声明为私有,将所有方法声明为 final,但是继承有什么用呢?

于 2008-09-28T17:46:23.753 回答
9

因为如果该类是最终的,则您无法扩展它并使其可变。

即使您将字段设置为最终字段,这仅意味着您不能重新分配引用,并不意味着您不能更改引用的对象。

我看不到在设计中也应该扩展的不可变类有很多用途,因此 final 有助于保持不变性完好无损。

于 2008-09-28T17:36:51.810 回答
6

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

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

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

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

这是他们在 DoRequest(MyUrlClass url) 方法中放入的内容:

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

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

顺便说一句,我对 evilhackers.org 没有任何反对意见,如果它存在的话......

在没有安全问题的情况下,一切都是为了避免编程错误,当然这取决于您如何做到这一点。子类必须遵守父类的契约,而不变性只是契约的一部分。但是,如果一个类的实例应该是不可变的,那么将其设为 final 是确保它们确实都是不可变的一种好方法(即,不存在子类的可变实例,它可以在父类的任何地方使用上课)。

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

于 2008-09-28T18:05:21.913 回答
0

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

我同意 Effective Java 在这些问题上的观点——你应该设计你的类以实现可扩展性或使它们不可扩展。如果您打算使某些东西可扩展,则可以考虑使用接口或抽象类。

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

于 2010-01-27T10:54:16.813 回答
0

那么,为什么不可变性是使类成为 final 的一个很好的理由呢?

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

所以其中一点指出

使类不可变类应标记为 final 或具有私有构造函数

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

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

  2. 将所有字段设为最终字段和私有字段。

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

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

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