构造函数中可覆盖的方法调用有什么问题? [英] What's wrong with overridable method calls in constructors?

查看:42
本文介绍了构造函数中可覆盖的方法调用有什么问题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 Wicket 页面类,它根据抽象方法的结果设置页面标题.

I have a Wicket page class that sets the page title depending on the result of an abstract method.

public abstract class BasicPage extends WebPage {

    public BasicPage() {
        add(new Label("title", getTitle()));
    }

    protected abstract String getTitle();

}

NetBeans 用消息构造函数中的可覆盖方法调用"警告我,但它应该有什么问题?我能想象的唯一替代方法是将其他抽象方法的结果传递给子类中的超级构造函数.但是如果有很多参数,这可能很难阅读.

NetBeans warns me with the message "Overridable method call in constructor", but what should be wrong with it? The only alternative I can imagine is to pass the results of otherwise abstract methods to the super constructor in subclasses. But that could be hard to read with many parameters.

推荐答案

关于从构造函数调用可重写方法

简单地说,这是错误的,因为它不必要地为许多个错误打开了可能性.当@Override被调用时,对象的状态可能不一致和/或不完整.

On invoking overridable method from constructors

Simply put, this is wrong because it unnecessarily opens up possibilities to MANY bugs. When the @Override is invoked, the state of the object may be inconsistent and/or incomplete.

引自Effective Java 2nd Edition, Item 17: Design and document for继承,否则禁止它:

为了允许继承,类必须遵守更多限制.构造函数不得直接或间接调用可覆盖的方法.如果违反此规则,将导致程序失败.超类构造函数在子类构造函数之前运行,因此子类中的覆盖方法将在子类构造函数运行之前被调用.如果覆盖方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行.

There are a few more restrictions that a class must obey to allow inheritance. Constructors must not invoke overridable methods, directly or indirectly. If you violate this rule, program failure will result. The superclass constructor runs before the subclass constructor, so the overriding method in the subclass will be invoked before the subclass constructor has run. If the overriding method depends on any initialization performed by the subclass constructor, the method will not behave as expected.

这里有一个例子来说明:

Here's an example to illustrate:

public class ConstructorCallsOverride {
    public static void main(String[] args) {

        abstract class Base {
            Base() {
                overrideMe();
            }
            abstract void overrideMe(); 
        }

        class Child extends Base {

            final int x;

            Child(int x) {
                this.x = x;
            }

            @Override
            void overrideMe() {
                System.out.println(x);
            }
        }
        new Child(42); // prints "0"
    }
}

这里,当Base构造函数调用overrideMe时,Child还没有完成对final int x的初始化,并且该方法获取错误的值.这几乎肯定会导致错误和错误.

Here, when Base constructor calls overrideMe, Child has not finished initializing the final int x, and the method gets the wrong value. This will almost certainly lead to bugs and errors.

具有许多参数的构造函数会导致可读性差,但存在更好的替代方案.

Constructors with many parameters can lead to poor readability, and better alternatives exist.

这里引用了 Effective Java 2nd Edition, Item 2:当面对许多构造函数参数时考虑构建器模式:

传统上,程序员使用了伸缩构造函数模式,在这种模式中,您提供一个仅带有必需参数的构造函数,另一个带有单个可选参数的构造函数,第三个带有两个可选参数的构造函数,依此类推...

Traditionally, programmers have used the telescoping constructor pattern, in which you provide a constructor with only the required parameters, another with a single optional parameters, a third with two optional parameters, and so on...

伸缩构造函数模式本质上是这样的:

The telescoping constructor pattern is essentially something like this:

public class Telescope {
    final String name;
    final int levels;
    final boolean isAdjustable;

    public Telescope(String name) {
        this(name, 5);
    }
    public Telescope(String name, int levels) {
        this(name, levels, false);
    }
    public Telescope(String name, int levels, boolean isAdjustable) {       
        this.name = name;
        this.levels = levels;
        this.isAdjustable = isAdjustable;
    }
}

现在您可以执行以下任一操作:

And now you can do any of the following:

new Telescope("X/1999");
new Telescope("X/1999", 13);
new Telescope("X/1999", 13, true);

但是,您目前不能仅设置 nameisAdjustable,而将 levels 保留为默认值.你可以提供更多的构造函数重载,但显然这个数字会随着参数数量的增加而爆炸,你甚至可能有多个 booleanint 参数,这真的会让一个把事情搞砸了.

You can't, however, currently set only the name and isAdjustable, and leaving levels at default. You can provide more constructor overloads, but obviously the number would explode as the number of parameters grow, and you may even have multiple boolean and int arguments, which would really make a mess out of things.

如您所见,这不是一个令人愉快的书写模式,使用起来更不愉快(这里的真"是什么意思?13 是什么?).

As you can see, this isn't a pleasant pattern to write, and even less pleasant to use (What does "true" mean here? What's 13?).

Bloch 建议使用构建器模式,这将允许您编写如下代码:

Bloch recommends using a builder pattern, which would allow you to write something like this instead:

Telescope telly = new Telescope.Builder("X/1999").setAdjustable(true).build();

请注意,现在参数已命名,您可以按照您想要的任何顺序设置它们,并且您可以跳过要保留默认值的参数.这当然比伸缩构造函数好得多,尤其是当存在大量属于许多相同类型的参数时.

Note that now the parameters are named, and you can set them in any order you want, and you can skip the ones that you want to keep at default values. This is certainly much better than telescoping constructors, especially when there's a huge number of parameters that belong to many of the same types.

这篇关于构造函数中可覆盖的方法调用有什么问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆