Java 构造函数中可覆盖的方法调用有什么问题?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3404301/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 22:59:49  来源:igfitidea点击:

What's wrong with overridable method calls in constructors?

javaoopinheritanceconstructoroverriding

提问by deamon

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

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

public abstract class BasicPage extends WebPage {

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

    protected abstract String getTitle();

}

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.

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

采纳答案by polygenelubricants

On invoking overridable method from constructors

从构造函数调用可覆盖的方法

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

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

A quote from Effective Java 2nd Edition, Item 17: Design and document for inheritance, or else prohibit it:

引自Effective Java 2nd Edition,第 17 项:继承的设计和文档,否则禁止它

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"
    }
}

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

这里,当Base构造函数调用时overrideMeChild还没有完成初始化final int x,方法得到了错误的值。这几乎肯定会导致错误和错误。

Related questions

相关问题

See also

也可以看看



On object construction with many parameters

关于具有许多参数的对象构造

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

具有许多参数的构造函数会导致可读性差,并且存在更好的替代方法。

Here's a quote from Effective Java 2nd Edition, Item 2: Consider a builder pattern when faced with many constructor parameters:

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

Traditionally, programmers have used the telescoping constructorpattern, 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);

You can't, however, currently set only the nameand isAdjustable, and leaving levelsat 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 booleanand intarguments, which would really make a mess out of things.

但是,您目前不能仅设置nameisAdjustable,并保留levels默认值。你可以提供更多的构造函数重载,但显然随着参数数量的增加,这个数字会爆炸,你甚至可能有多个booleanint参数,这真的会让事情变得一团糟。

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?).

正如您所看到的,这不是一个令人愉快的编写模式,使用起来更不愉快(“true”在这里是什么意思?13 是什么?)。

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

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

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.

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

See also

也可以看看

Related questions

相关问题

回答by Manuel Selva

If you call methods in your constructor that subclasses override, it means you are less likely to be referencing variables that don't exist yet if you divide your initialization logically between the constructor and the method.

如果在子类覆盖的构造函数中调用方法,则意味着如果在构造函数和方法之间按逻辑划分初始化,则不太可能引用尚不存在的变量。

Have a look on this sample link http://www.javapractices.com/topic/TopicAction.do?Id=215

看看这个示例链接http://www.javapractices.com/topic/TopicAction.do?Id=215

回答by KeatsPeeks

Invoking an overridable method in the constructor allows subclasses to subvert the code, so you can't guarantee that it works anymore. That's why you get a warning.

在构造函数中调用一个可覆盖的方法允许子类颠覆代码,所以你不能保证它再工作。这就是您收到警告的原因。

In your example, what happens if a subclass overrides getTitle()and returns null ?

在您的示例中,如果子类覆盖getTitle()并返回 null会发生什么?

To "fix" this, you can use a factory methodinstead of a constructor, it's a common pattern of objects instanciation.

要“修复”此问题,您可以使用工厂方法而不是构造函数,这是对象实例化的常见模式。

回答by Nordic Mainframe

Here's an example which helps to understand this:

这是一个有助于理解这一点的示例:

public class Main {
    static abstract class A {
        abstract void foo();
        A() {
            System.out.println("Constructing A");
            foo();
        }
    }

    static class C extends A {
        C() { 
            System.out.println("Constructing C");
        }
        void foo() { 
            System.out.println("Using C"); 
        }
    }

    public static void main(String[] args) {
        C c = new C(); 
    }
}

If you run this code, you get the following output:

如果您运行此代码,您将获得以下输出:

Constructing A
Using C
Constructing C

You see? foo()makes use of C before C's constructor has been run. If foo()requires C to have a defined state (i.e. the constructor has finished), then it will encounter an undefined state in C and things might break. And since you can't know in A what the overwritten foo()expects, you get a warning.

你看?foo()在运行 C 的构造函数之前使用 C。如果foo()要求 C 具有已定义的状态(即构造函数已完成),那么它将在 C 中遇到未定义的状态并且事情可能会中断。并且由于您无法在 A 中知道被覆盖的foo()预期内容,因此您会收到警告。

回答by kirilv

Here is an example that reveals the logical problemsthat can occur when calling an overridable method in the super constructor.

这是一个示例,它揭示了在超级构造函数中调用可重写方法时可能发生的逻辑问题

class A {

    protected int minWeeklySalary;
    protected int maxWeeklySalary;

    protected static final int MIN = 1000;
    protected static final int MAX = 2000;

    public A() {
        setSalaryRange();
    }

    protected void setSalaryRange() {
        throw new RuntimeException("not implemented");
    }

    public void pr() {
        System.out.println("minWeeklySalary: " + minWeeklySalary);
        System.out.println("maxWeeklySalary: " + maxWeeklySalary);
    }
}

class B extends A {

    private int factor = 1;

    public B(int _factor) {
        this.factor = _factor;
    }

    @Override
    protected void setSalaryRange() {
        this.minWeeklySalary = MIN * this.factor;
        this.maxWeeklySalary = MAX * this.factor;
    }
}

public static void main(String[] args) {
    B b = new B(2);
    b.pr();
}

The result would actually be:

结果实际上是:

minWeeklySalary: 0

最低周薪:0

maxWeeklySalary: 0

最大周薪:0

This is because the constructor of class B first calls the constructor of class A, where the overridable method inside B gets executed. But inside the method we are using the instance variable factorwhich has not yet been initialized(because the constructor of A has not yet finished), thus factor is 0 and not 1 and definitely not 2 (the thing that the programmer might think it will be). Imagine how hard would be to track an error if the calculation logic was ten times more twisted.

这是因为类 B 的构造函数首先调用类 A 的构造函数,在那里执行 B 中的可覆盖方法。但是,该方法中,我们使用的是实例变量因素尚未初始化(因为A的构造尚未完成),从而系数为0而不是1,绝对不是2(程序员可能会认为这会事是)。想象一下,如果计算逻辑扭曲十倍,那么跟踪错误会有多困难。

I hope that would help someone.

我希望这会帮助某人。

回答by sanluck

I guess for Wicket it's better to call addmethod in the onInitialize()(see components lifecycle) :

我想对于 Wicket 最好addonInitialize()(请参阅组件生命周期)中调用方法:

public abstract class BasicPage extends WebPage {

    public BasicPage() {
    }

    @Override
    public void onInitialize() {
        add(new Label("title", getTitle()));
    }

    protected abstract String getTitle();
}

回答by Volksman

In the specific case of Wicket: This is the very reason why I asked the Wicket devs to add support for an explicit two phase component initialization process in the framework's lifecycle of constructing a component i.e.

在 Wicket 的特定情况下:这就是为什么我要求 Wicket 开发人员在构建组件的框架生命周期中添加对显式两阶段组件初始化过程的支持的原因,即

  1. Construction - via constructor
  2. Initialization - via onInitilize (after construction when virtual methods work!)
  1. 构造 - 通过构造函数
  2. 初始化 - 通过 onInitilize(在虚方法工作时构造之后!)

There was quite an active debate about whether it was necessary or not (it fully is necessary IMHO) as this link demonstrates http://apache-wicket.1842946.n4.nabble.com/VOTE-WICKET-3218-Component-onInitialize-is-broken-for-Pages-td3341090i20.html)

关于是否有必要(恕我直言,这完全是必要的)进行了相当活跃的辩论,因为此链接演示了http://apache-wicket.1842946.n4.nabble.com/VOTE-WICKET-3218-Component-onInitialize- is-broken-for-Pages-td3341090i20.html)

The good news is that the excellent devs at Wicket did end up introducing two phase initialization (to make the most aweseome Java UI framework even more awesome!) so with Wicket you can do all your post construction initialization in the onInitialize method that is called by the framework automatically if you override it - at this point in the lifecycle of your component its constructor has completed its work so virtual methods work as expected.

好消息是,Wicket 的优秀开发者确实最终引入了两阶段初始化(使最棒的 Java UI 框架更加出色!)所以使用 Wicket,您可以在调用的 onInitialize 方法中完成所有后期构造初始化如果你覆盖它,框架会自动 - 在你的组件生命周期的这一点上,它的构造函数已经完成了它的工作,所以虚拟方法按预期工作。