它在this article中说:
让一个类成为最终因为它是不可变的是一个很好的理由。
我对此感到有点困惑......我理解不变性是线程安全性和简单性的POV中的好东西,但似乎这些问题与可扩展性有些正交。那么,为什么不变性是让一个班级最终成为一个很好的理由呢?
答案 0 :(得分:11)
“Effective Java”一书中给出了对此的解释
考虑Java中的BigDecimal
和BigInteger
类。
人们普遍不了解不可变类必须是最终的
当BigInteger
和BigDecimal
被写入时,他们的所有方法都可以
覆盖。不幸的是,在保留向后兼容性的同时,这无法得到纠正。
如果编写一个类,其安全性取决于来自不受信任的客户端的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文档)
不要提供" setter"方法 - 修改字段引用的字段或对象的方法。
将所有字段设为最终字段并保密。
不允许子类覆盖方法。最简单的方法是将类声明为final。更复杂的方法是使构造函数私有,并在工厂方法中构造实例。
如果实例字段包含对可变对象的引用,则不允许更改这些对象: