它在本文中说:
让一个类成为最终因为它是不可变的是这样做的一个很好的理由.
我对此感到有点困惑......我理解不变性是线程安全性和简单性的POV中的好东西,但似乎这些问题与可扩展性有些正交.那么,为什么不变性成为最终上课的一个很好的理由呢?
"有效Java"一书中给出了对此的解释.
考虑BigDecimal
和BigInteger
Java中的类.
它并没有被广泛了解,一成不变的类必须是有效时,最终BigInteger
和BigDecimal
编写,因此他们所有的方法可能会被改写.不幸的是,在保留向后兼容性的同时,这无法得到纠正.
如果编写一个类,其安全性取决于来自不可信客户端的BigInteger或BigDecimal参数的不变性,则必须检查该参数是否为"真实"BigInteger或BigDecimal,而不是不受信任的子类的实例.如果是后者,则必须在假设它可能是可变的情况下进行防御性复制.
public static BigInteger safeInstance(BigInteger val) { if (val.getClass() != BigInteger.class) return new BigInteger(val.toByteArray()); return val; }
如果允许子类,则可能会破坏不可变对象的"纯度".
根据Liskov替换原则,子类可以延伸但从不重新定义其父类的合同.如果基类是不可变的,那么很难找到其功能可以在不违反合同的情况下有效扩展的示例.
请注意,原则上可以扩展不可变类并更改基本字段,例如,如果基类包含对数组的引用,则不能将数组中的元素声明为final.显然,方法的语义也可以通过覆盖来改变.
我想你可以将所有字段声明为私有,并将所有方法声明为final,但那么继承的用途是什么?
因为如果这个类是最终的,你不能扩展它并使它变得可变.
即使您将字段设为final,这只意味着您无法重新分配引用,也不意味着您无法更改引用的对象.
我并没有在设计中看到很多用于不可变类的应该扩展的类,所以final有助于保持不变性.
我认为主要是安全性.出于同样的原因,String是final,任何与安全相关的代码都希望将其视为不可变的东西必须是最终的.
假设您有一个定义为不可变的类,请将其命名为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是一种很好的方法来确保它们确实都是不可变的(即没有可变的子类实例,它们可以在父类的任何地方使用类被称为).
我不认为你引用的文章应该被视为"所有不可变类必须是最终的"的指令,特别是如果你有充分的理由设计你的不可变类继承.它所说的是保护不变性是最终的一个正当理由,其中虚构的性能问题(这是它在那时真正谈论的)是无效的.请注意,它给出了"一个不是为继承而设计的复杂类"作为同样有效的理由.可以肯定的是,在复杂类中没有考虑继承是一件可以避免的事情,就像在不可变类中没有考虑继承一样.但如果你不能解释它,你至少可以通过阻止它来表明这一事实.