如何强制执行超类方法的多态调用?

13

我有一个init方法,该方法被广泛继承和覆盖。然而每个init调用都会延伸到先前工作的基础上。因此,我会:

@Override public void init() {
   super.init();
}

当然,这将确保调用和实例化所有内容。我的疑问是:我能创建一种方法来确保已调用超级方法吗?如果没有调用所有的init,对象就会崩溃,因此如果有人忘记调用super,我想抛出异常或错误。

TYFT ~Aedon


请强制派生类调用超类方法,就像Android一样,并强制基本方法调用。 - Ted Hopp
@Ted - 你发布的第一个链接看起来不像是重复的,另一个问题涉及到Eclipse自动生成的方法存根,而另一个问题则是关于C#而不是Java。 - Andreas Dolk
@Andreas - 第一个问题的答案展示了几种强制调用超类的方法。第二个问题标记了C#和Java,答案是各种解决方案的混合。 - Ted Hopp
你可能可以为这样的东西创建自己的注释。 - Amir Afghani
@Amir - 不好意思,我应该如何创建一个注释来执行这样的操作?我对注释一般不太熟悉。 - ahodder
@Aedon,抱歉因为我不是一个注释专家而显得无知。你需要阅读有关它的资料-但基本想法是你的注释将在执行之前静态分析代码。 - Amir Afghani
7个回答

13

不要试图这样做 - 我认为这是不可行的! - 要不尝试另一种方法:

abstract class Base {
 public final void baseFunction() {
   ...
   overridenFunction(); //call the function in your base class
   ...
 }

 public abstract void overridenFunction();
}
...
class Child extends Base {
 public void overridenFunction() {...};
}

...
Base object = new Child();
object.baseFunction(); //this now calls your base class function and the overridenFunction in the child class!

这个方案是否适合你?


通过覆盖overridenFunction并调用多态的overridenFunction,可以实现相同的功能。创建baseFunction方法没有任何意义。 - ahodder
4
对于第二代派生类,这不起作用。如果您定义class Grandchild extends Child,那么您不能保证Grandchild.overridenFunction调用Child.overridenFunction - Ted Hopp
1
@Ted - 对的,这就是我要解决的问题。如果Class调用foo,那么ChildClass会覆盖foo并且无法调用super.foo,当GrandChildClass尝试调用super.foo时,就会出现层次结构上的问题。这就是我要阻止的。 - ahodder
优秀;虽然像 init()childInit() 这样的不同方法名称可能有助于更好地传达意图。 - matt b
1
@Ted,我认为最好的方法是使用不同的名称,比如在这里使用grandchildInit()。在Java中,唯一可以强制执行OP所需行为的情况是构造函数链。如果对象不能在构造时初始化,我们就会得到这种设置。请参见http://en.wikipedia.org/wiki/Call_super。 - Andrew Lazarus
显示剩余2条评论

9
这是一种在派生类未能调用超类时引发异常的方法:
public class Base {
    private boolean called;
    public Base() { // Doesn't have to be the c'tor; works elsewhere as well.
                    // In fact, shouldn't call overridable methods from c'tor.
        called = false;
        init();
        if (!called) {
            // throw an exception
        }
    }
    protected void init() {
        called = true;
        // other stuff
    }
}

我喜欢这个。我认为需要稍微调整一下才能与我的工作融合,并且它仍然是运行时的。但是,如果我希望实现的目标不可能实现,我觉得这将非常有效。 - ahodder
1
我认为通用问题没有编译时解决方案。其他人提出的使用“final”初始化方法的解决方案仅在基类init逻辑被执行是唯一关注点的情况下有效。这是我知道的唯一一种检查派生类方法是否调用了其“super”方法的方法。(我猜甚至可以通过显式调用“Base.this.init()”来跳过“Child.init()”而打破它,但那只是邪恶的。) - Ted Hopp
没错。我认为这(以及类似的)是更好的解决方案。谢谢。 - ahodder
在这篇文章中,关于编译时执行的建议很少:https://dev59.com/FFMI5IYBdhLWcg3wBGqC - morgwai

2

实际上,Android 是通过 Activity 类来实现这一点的。我不确定他们是否需要在运行时中构建支持,但我建议查看 Activity 类的开源代码实现。具体来说,在任何生命周期方法中,您必须在执行任何操作之前调用相应的父类方法,否则它会抛出 SuperNotCalledException 异常。

例如,在 onCreate() 中,您首先必须调用 super.onCreate()


@ Andreas_D - 是的先生。在运行时之前出现编译错误会很好,可以节省一些测试时间。 - ahodder
2
@Andreas - “我想抛出一个异常或错误”听起来不像OP期望的是编译时解决方案。 - Ted Hopp

1

我经常喜欢使用这个解决方案。它不会抛出运行时错误,但会显示语法错误:

 @CallSuper
 public void init() {
     // do stuff
 }

这是Android支持注释的一部分。

0
让继承树顶部的类在初始化时设置一个标志。然后,继承树底部的类可以检查该标志以查看是否已经遍历了整个树。我会制作文档,指出每个base的子元素都应包含以下初始化代码:
super.init()
if (!_baseIsInitialized) {
    // throw exception or do w/e you wish
}

基础使用在哪里

_baseIsInitialized = true;

另一方面,强制你的孩子调用super.init()会更加困难,并且很可能包含丑陋的黑客技巧。

1
但是如何强制子类检查呢? - jmj
有趣的解决方案。这是唯一的方法吗?我希望(也许有点天真)能有更优雅的解决方案。 - ahodder
@Jigar Joshi:这是一个棘手的问题。 - orlp
@Jigar:我认为这并不容易实现,而且根本无法在编译时实现。无法告诉编译器这样的事情:“在对child.init进行标记化后,请检查它是否包含super.init(),如果没有,则抛出错误”。 - orlp

0

我不知道有什么方法可以使用一个方法来实现这个。

然而,请注意这正是构造函数的工作方式。每个构造函数必须直接或间接地调用其超类的构造函数。这是静态保证的。

我注意到你正在编写一个init方法。你能否重构代码,使其使用构造函数而不是init方法?这将使你立即获得这种行为。有些人(比如我)更喜欢构造函数而不是init方法,部分原因就在于此。

请注意,使用构造函数而不是init方法可能并不意味着在当前查看的类上使用它们 - 可能会进行重构,将需要初始化状态移动到一个平行的类层次结构中,该层次结构可以使用适当的构造函数。


0

现在你可以使用@CallSuper注解你的方法。这将进行Lint检查,以确保对该方法的任何重写都调用了super()。以下是一个示例:

@CallSuper
protected void onAfterAttached(Activity activity) {
    if (activity instanceof ActivityMain) {
        mainActivity = (ActivityMain) activity;
    }
}

在上面的示例中,任何重写onAfterAttached但不调用super的子类中的方法都会使Lint引发错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接