【问题标题】:Good method to make it obvious that an overriden method should call super?使被覆盖的方法应该调用 super 的好方法?
【发布时间】:2011-11-13 02:22:03
【问题描述】:

这个问题让我很头疼,重写方法时很容易忘记调用 super()。

在我的例子中,我正在重构一些现有的东西,其中已经有大约十个类覆盖了一个方法。直到昨天,该方法还有一个空的默认实现,所以子类是否调用 super 并不重要。 您可以使用任何值得其盐的 IDE 找到覆盖器,但您知道它是怎样的,电话响了,同事在您背后闲聊……很容易忘记检查一个地方或以其他方式忽略它。 理想情况下,@Override 注释会有一个对应项,如果基类方法被注释但覆盖没有调用 super,编译器会为这些地方生成警告。

我能做的下一件最好的事情是什么?

【问题讨论】:

  • 拔掉手机电源,别再偷听smalltalks了!
  • 某些 IDE 允许您搜索“拒绝遗赠”等。您可以使其显示为错误(尽管它不会停止构建)您可以在特定情况下选择性地忽略此警告。
  • 我不确定,但是像 FindBugs、PMD 之类的工具不是都有这样的规则吗?

标签: java annotations overriding super


【解决方案1】:

不是很优雅,但可能的解决方案是将该方法一分为二:

public abstract class Foo {
    public void doBar() {
        // do your super logic
        doBarInternal();
    }

    public abstract void doBarInternal();
}

【讨论】:

  • 我会说它相当优雅 - 这是模板方法模式,基本上。
  • 不是确保已经存在的代码真正调用 super 的选项,是吗?这在理论上可能很好,但现实却很糟糕:你不能重构 3rd 方的东西。
  • @Durandal 既然你提到了 3rd 方的东西 阻止重构,anticorruption layer 自然会浮现在脑海中。在这样一个层内,人们可以安全地封装所需的任何检查和补丁,并保持自己的代码干净
  • @Durandal - 但您实际上在问题中给出了一个示例,说明如何向基类添加注释。如何将注释添加到 3rd 方类?考虑一下......也许答案是:添加一个包含上述解决方案的委托类,并且只允许使用该类。
  • 在我的具体情况下,确实基类是我的来源。但是我忘记调用super的方法是接口的实现,这不是我的源代码,调用该接口的代码也不是。但这不是重点,我正在寻找一个相当快速和简单的解决方案。重构十几个类以避免犯一种错误,同时为一堆新错误打开机会,这不是我解决问题的方法。
【解决方案2】:

如果必须始终调用超类实现,则可以使用“模板方法”模式。

所以你现在拥有的是这样的:

public static class Parent {
    public void doSomething() {
        System.out.println("Parent doing something");
    }
}

public static class Child extends Parent {
    public void doSomething() {
        // MUST NOT FORGET SUPER CALL
        super.doSomething();
        System.out.println("Child doing something");
    }
}

public static void main(String[] args) {
    Child child = new Child();
    child.doSomething();
}

这将变成:

public abstract static class Parent {
    public final void doSomething() {
        System.out.println("Parent doing something");
        childDoSomething();
    }

    public abstract void childDoSomething();
}

public static class Child extends Parent {
    public void childDoSomething() {
        System.out.println("Child doing something");
    }
}

public static void main(String[] args) {
    Child child = new Child();
    child.doSomething();
}

(类被设为静态以便在一个类中进行测试)

我将 doSomething 设为 final 以避免它被覆盖,因为在此解决方案中应该实现 childDoSomething。

当然,这个解决方案意味着 Parent 不能再用作具体类。

编辑:在阅读了有关 Child 实现第三方接口的 cmets 之后;这不是问题:

public interface ThirdPartyInterface {
    public void doSomething();
}

public abstract static class Parent {
    public final void doSomething() {
        System.out.println("Parent doing something");
        childDoSomething();
    }

    public abstract void childDoSomething();
}

public static class Child extends Parent implements ThirdPartyInterface{
    public void childDoSomething() {
        System.out.println("Child doing something");
    }

//    public final void doSomething() {
//        // cannot do this because Parent makes it final
//    }
}

public static void main(String[] args) {
    Child child = new Child();
    child.doSomething();
}    

【讨论】:

    【解决方案3】:

    寻找其他我在 FindBugs 中发现有趣的 OverrideMustInvoke 注释http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/OverrideMustInvoke.html

    【讨论】:

      【解决方案4】:

      如果您不坚持编译时安全,您可以使用一种机制,只要子类的行为不符合逻辑要求,就会引发异常:How do I force a polymorphic call to the super method?

      【讨论】:

        【解决方案5】:

        我有两个不同的建议:

        1) 构建一个 junit 测试,发现基类的所有子类,然后选择所有用 @Override 注释修饰的方法。我有一些执行类似操作的单元测试(例如,查找所有子类,检查它们是否真正是可序列化的)。

        不幸的是,验证他们是否称“超级”有点不那么简单。您要么需要让测试查找源文件并搜索它,要么更好(但我不知道该怎么做),读取字节码并查看是否可以检测到对 super.xml 的调用。

        2) 需要保证调用 super 可能是设计/接口问题的指标,而不是编码/实现问题。如果真的要保证用户调用super,最好把super类做成abstract,明确指定一个抽象实现方法供他们重写,让super控制执行流程。

        如果你想定义一个默认实现,以便不是所有用户都需要子类提供实现该方法,你可以定义一个默认实现类供人们使用。 (如果你真的想控制它,将默认的类实现方法定义为 final 以强制它们回到抽象父类的子类化。)

        代码重用继承总是更难管理,因此需要谨慎处理。任何进行代码重用继承的人都必须对超类的内部有一个很好的了解才能正确地做到这一点(这有点令人讨厌)。例如,您是否必须在覆盖代码的开头或结尾调用 super.method()? (或者你可以在中间做......)

        总而言之,最好的解决方案是尽量避免必须强制执行的设计。

        【讨论】:

          【解决方案6】:

          可行的方法 - 创建一些标记注释(即@MustCallParent),然后创建一些注释处理器来检查用它标记的方法是否符合约束。

          【讨论】:

          • 反之亦然:父方法上的@SubclassesMustCallSuper注解。
          • 我对注解了解不多,但我怀疑他们是否可以检查 super.method() 是否已在覆盖方法中被调用。
          • 我不明白为什么以这种方式收集带有以这种方式标记的类的文件列表并进行或多或少复杂的代码分析会是一个问题。
          • @toto2 一个注解处理器(在 jdk7+8 中)可以访问整个 java 编译过程的 AST 树,因此可以知道是否调用了 super.method()。
          猜你喜欢
          • 2011-12-13
          • 2020-09-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-05-01
          • 2023-01-07
          • 1970-01-01
          相关资源
          最近更新 更多