Java 不可变类应该是最终类吗?

Java 不可变类应该是最终类吗?,java,oop,final,Java,Oop,Final,它说: 将一个类设置为final(因为它是不可变的)是这样做的一个很好的理由 我对此有点困惑。。。我从线程安全性和简单性的角度理解不变性是一件好事,但这些关注点似乎与可扩展性有点正交。那么,为什么不变性是使类成为最终类的一个很好的理由呢?因为如果类是最终类,您就不能扩展它并使其可变 即使将字段设置为最终字段,这也只意味着无法重新指定引用,但并不意味着无法更改引用的对象 我看不出在设计一个也应该扩展的不可变类时有多大用处,所以final有助于保持不可变性。遵循Liskov替换原则,子类可以扩展,但

它说:

将一个类设置为final(因为它是不可变的)是这样做的一个很好的理由


我对此有点困惑。。。我从线程安全性和简单性的角度理解不变性是一件好事,但这些关注点似乎与可扩展性有点正交。那么,为什么不变性是使类成为最终类的一个很好的理由呢?

因为如果类是最终类,您就不能扩展它并使其可变

即使将字段设置为最终字段,这也只意味着无法重新指定引用,但并不意味着无法更改引用的对象


我看不出在设计一个也应该扩展的不可变类时有多大用处,所以final有助于保持不可变性。

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

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


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

我认为主要是安全性。出于同样的原因,字符串是final,任何安全相关代码想要视为不可变的内容都必须是final

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

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

void checkUrl(MyUrlClass testurl) throws SecurityException {
    if (illegalDomains.contains(testurl.getDomain())) throw new SecurityException();
}
下面是他们在DoRequest(MyUrlClass url)方法中的内容:

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

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

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


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

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

我同意有效Java在这些问题上所采取的立场——要么设计类以实现可扩展性,要么使其不可扩展。如果你想让某事物扩展,也许考虑一个接口或抽象类。


此外,你不必参加期末考试。您可以将构造函数设置为私有的。

这方面的解释在《有效Java》一书中给出

考虑Java中的
BigDecimal
biginger

不可变的类必须是有效的最终类,这一点并没有得到广泛的理解 当写入
biginger
BigDecimal
时,它们的所有方法都可能是 被推翻。不幸的是,在保留向后兼容性的情况下,这无法在事后纠正

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

   public static BigInteger safeInstance(BigInteger val) {

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

    return new BigInteger(val.toByteArray());


       return val;

   }

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

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

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

其中一点是这样的

要使类不可变,应将类标记为final或have private constructor

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

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

  • 将所有字段设置为最终字段和私有字段

  • 不允许子类重写方法。最简单的方法是将类声明为final。更多
       public static BigInteger safeInstance(BigInteger val) {
    
       if (val.getClass() != BigInteger.class)
    
        return new BigInteger(val.toByteArray());
    
    
           return val;
    
       }