【问题标题】:immutable class should be final?不可变类应该是最终的?
【发布时间】:2010-09-13 20:43:14
【问题描述】:

它在this article 中说:

因为它是不可变的而将类设为 final 是一个很好的理由。

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

【问题讨论】:

    标签: java oop final


    【解决方案1】:

    《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;
    
       }
    

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

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

      • 假设你有一个不可变的 Shape 类,它有一个 area 属性。您可能想要创建一个具有附加属性(例如半径)的 Circle 子类。这些新属性本身可能是不可变的,因此保留了父级的不变性。这有什么问题?
      • 我不认为这有什么问题,只要你是唯一从Shape延伸出来的人。但是您必须为其他开发人员制定精确的指导方针,并确信他们会遵循这些指导方针。所以我认为这个论点是 99.9% 的时间它不值得努力......
      • GG 很赚钱。如果你做出保证,你必须保持它。
      • @Don Shape 在那种情况下不会是一个抽象类吗?
      【解决方案3】:

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

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

      我认为在设计中应该扩展不可变类的用途并不多,因此 final 有助于保持不变性不变。

      【讨论】:

      • 但您可能希望扩展一个不可变类以添加其他不可变属性。
      • "因为如果类是 final 你不能扩展它并使其可变" 但是你不能扩展一个非 final 的不可变类并使其可变,你只能使子类可变
      • 作为调用者,你不知道你使用的是不可变类还是它的可变子类——除非你碰巧直接调用了构造函数。这就是多态性。
      【解决方案4】:

      我认为主要是安全性。出于同样的原因,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 的一个正当理由,其中想象中的性能问题(这是它当时真正谈论的)是无效的。请注意,它给出了“一个不是为继承而设计的复杂类”作为同样有效的理由。可以公平地说,不考虑复杂类中的继承是应该避免的,就像不考虑不可变类中的继承一样。但如果你不能解释它,你至少可以通过阻止它来表明这个事实。

      【讨论】:

      • 正如我所说,“可以公平地说,在复杂类中不考虑继承是应该避免的……但如果你不能解释它,你至少可以发出信号通过防止它来实现这一事实。”。而你正在争论它。相当。
      【解决方案5】:

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

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

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

      【讨论】:

      • 只是一些笔记。 1)通常,与等效的不可变实例相比,出于性能原因,可变类实例更好。不可变实例通常是线程安全的,但如果您想获得状态稍有不同的更新对象(使用原型或构建器模式),则不可变性会付出代价。 2)Integer.valueOf 是一个 Flightweight 模式示例,该模式通常与不可变性的问题无关。 3) 拥有私有构造函数并不能阻止使用内部类进行子类化。
      【解决方案6】:

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

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

      所以其中一个观点是

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

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

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 2018-08-23
        • 1970-01-01
        • 2020-01-15
        • 1970-01-01
        • 1970-01-01
        • 2019-05-15
        • 1970-01-01
        • 2015-08-01
        • 1970-01-01
        相关资源
        最近更新 更多