【问题标题】:Why avoid the final keyword?为什么要避免使用 final 关键字?
【发布时间】:2009-02-06 11:10:44
【问题描述】:

在java中,有没有允许扩展非抽象类的情况?

当有类层次结构时,它似乎总是表明代码错误。你同意吗?为什么/为什么不同意?

【问题讨论】:

    标签: java inheritance final extends


    【解决方案1】:

    在某些时候,有非最终的具体类是有意义的。但是,我同意 Kent 的观点——我相信类默认情况下应该是最终的(在 C# 中密封),而 Java 方法默认情况下应该是最终的(就像它们在 C# 中一样)。

    正如 Kent 所说,继承需要仔细的设计和文档 - 很容易认为您可以只覆盖单个方法,但不知道在哪些情况下可以从基类调用该方法作为其余部分的一部分实施。

    有关此问题的更多讨论,请参阅"How do you design a class for inheritance"

    【讨论】:

    • +1 这个以及不默认打包私有访问,没有检查异常,以及一大堆其他东西是 C# 正确的东西(与 Java 相比)恕我直言。我猜是第二个采用者的优势。
    【解决方案2】:

    我同意 Jon 和 Kent 的观点,但就像 Scott Myers(在 Effective C++ 中)一样,我走得更远。我相信每个班级都应该是abstractfinal。也就是说,只有任何层次结构中的叶类才真正适合直接实例化。所有其他类(即继承中的内部节点)都“未完成”,因此应该是abstract

    进一步扩展普通课程毫无意义。如果类的某个方面值得扩展和/或修改,更简洁的方法是采用该类并将其分离为一个 abstract 基类和一个具体的可互换实现。

    【讨论】:

    • 如果有 4 种不同的(合理的)虚方法,我想在除了其中一种之外都像实现 X 一样怎么办?您可以将其分为 4 个类,但这并不总是最干净的方法。您的路线听起来有点有点极端,但作为一般原则(续)
    • 换句话说 - 在某些情况下它可能值得违反,但前提是您仔细考虑了替代方案。
    • 太严格了。在某些情况下,类可以继承但可以实例化。 JButton 和 JLabel 怎么样?
    • 所有关于软件开发的绝对陈述至少有点错误。当然,包括我刚刚写的那一篇。
    • 不知道我是否完全同意你的看法。我重写了 .NET 的 Button 和 Textbox 控件来添加我自己的功能,这两个都是 BCL 中的非最终叶类。如果这些课程是最终课程,我会有什么选择?
    【解决方案3】:

    有充分的理由让您的代码保持非最终版本。许多框架,如 hibernate、spring、guice 有时依赖于它们在运行时动态扩展的非最终类。

    例如,hibernate 使用代理进行延迟关联获取。 特别是在涉及 AOP 时,您会希望您的类不是最终的,以便拦截器可以附加到它。 另见question at SO

    【讨论】:

      【解决方案4】:

      这个问题同样适用于其他平台,例如 C# .NET。有些人(包括我自己)认为默认类型应该是最终的/密封的,并且需要显式解封以允许继承。

      通过继承进行扩展是需要仔细设计的事情,而不是仅仅让一个类型不密封那么简单。因此,我认为允许继承应该是一个明确的决定。

      【讨论】:

        【解决方案5】:

        您最好的参考资料是 Joshua Bloch 的优秀著作《Effective Java》的第 15 条,名为“Design and document for inheritance or else implicit it”。然而,是否允许类扩展的关键不是“它是抽象的”,而是“它在设计时是否考虑到了继承”。有时两者之间存在相关性,但重要的是第二个。举个简单的例子,大多数 AWT 类都被设计为可扩展的,即使是那些非抽象类。

        Bloch 章节的总结是,如果祖先不是被设计为从其继承的,那么继承类与其父类的交互可能会令人惊讶和不可预测。因此,类应该有两种 a) 设计用于扩展的类,并且有足够的文档来描述它应该如何完成 b) 标记为 final 的类。 (a) 中的类通常是抽象的,但并非总是如此。对于

        【讨论】:

          【解决方案6】:

          我不同意。如果层次结构不好,则没有理由存在面向对象的语言。如果您查看 Microsoft 和 Sun 的 UI 小部件库,您肯定会发现继承。这就是定义上的所有“坏代码”吗?不,当然不是。

          继承可以被滥用,但任何语言特性也可以。诀窍是学习如何正确地做事。

          【讨论】:

          • 继承不是 OO 的重要组成部分。成分和多态性岩石。
          • 继承在 15 年前是现代的。今天我们知道继承往往会导致更多的问题,然后是组合。
          • 感谢亚历克斯在事后将近四年的时间里称重。也许您可以在这里回答一些问题并提高您的声誉,而不是像这样与 cmets 拖钓。
          【解决方案7】:

          在某些情况下,您希望确保没有子类化,在其他情况下,您希望确保子类化(抽象)。但是,作为原始作者,您总是不关心也不应该关心的大部分类。这是打开/关闭的一部分。决定应该关闭某事是有原因的。

          【讨论】:

            【解决方案8】:

            我完全不同意。当具体类知道它们未标记为 final 的方法的可能返回类型时,类层次结构对具体类有意义。例如,一个具体的类可能有一个子类钩子:

            protected SomeType doSomething() {
            return null;
            }
            

            此 doSomething 保证为 null 或 SomeType 实例。假设您有能力处理 SomeType 实例,但没有在当前类中使用 SomeType 实例的用例,但知道此功能在子类中非常有用,而且大多数内容都是具体的。如果当前类可以直接使用,并且默认不对其 null 值执行任何操作,那么将当前类设为抽象类是没有意义的。如果你把它做成一个抽象类,那么你就会在这种类型的层次结构中拥有它的子类:

            • 抽象基类
              • 默认类(可能是非抽象类,只实现受保护的方法,没有其他)
              • 其他子类。

            因此,您有一个不能直接使用的抽象基类,而默认类可能是最常见的情况。在另一个层次结构中,少了一个类,因此可以使用该功能而无需创建一个本质上无用的默认类,因为必须将抽象强加到该类上。

            • 默认类
              • 其他子类。

            现在,当然,可以使用和滥用层次结构,如果事情没有清楚地记录或类设计得不好,子类可能会遇到问题。但是抽象类也存在同样的问题,你不能仅仅因为你在你的类中添加了“抽象”就摆脱了这个问题。例如,如果上面的“doSomething()”方法的约定要求 SomeType 在通过 getter 和 setter 访问它们时填充 x、y 和 z 字段,那么无论您使用返回 null 的具体类,您的子类都会崩溃作为您的基类或抽象类。

            设计类层次结构的一般经验法则非常简单:

            1. 我的子类中是否需要我提议的超类的行为? (是/否) 这是您需要问自己的第一个问题。如果您不需要该行为,则无需进行子类化。

            2. 我的子类中是否需要我提议的超类的状态? (是/否) 这是第二个问题。如果状态符合您需要的模型,这可能是子类化的候选对象。

            3. 如果子类是从提议的超类创建的,它真的是一个 IS-A 关系,还是只是继承行为和状态的捷径? 这是最后一个问题。如果它只是一个捷径并且你不能限定你提议的子类“as-a”超类,那么应该避免继承。可以将状态和逻辑复制并粘贴到具有不同根的新类中,也可以使用委托。

            只有当一个类需要行为、状态并且可以认为是超类的子类IS-A(n)实例时,才应该认为它是从超类继承的。否则,存在其他更适合该目的的选项,尽管它可能需要更多的前期工作,但从长远来看它会更干净。

            【讨论】:

              【解决方案9】:

              在某些情况下,我们不希望允许更改行为。例如,String 类,Math。

              【讨论】:

              • 这当然不是真的。总是有改进实现的空间。 String 和 Math 中没有什么应该是 final 的。
              • 其实Math不应该有任何静态方法。您应该能够实例化它。如果你想改变行为,用你自己的覆盖数学。 (比如说一个快速但不准确的正弦函数。)
              【解决方案10】:

              我不喜欢继承,因为总是有更好的方法来做同样的事情,但是当您在一个庞大的系统中进行维护更改时,有时以最少更改修复代码的最佳方法是稍微扩展一个类.是的,它通常会导致一个糟糕的代码,但会导致一个有效的代码,并且无需先重写数月。因此,为维护人员提供尽可能多的灵活性是一个不错的方法。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2011-05-09
                • 2018-07-22
                • 2021-06-25
                • 1970-01-01
                • 2018-11-13
                • 2011-11-15
                相关资源
                最近更新 更多