构造函数中的可重写方法调用有什么问题?

2022-08-31 04:40:13

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

public abstract class BasicPage extends WebPage {

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

    protected abstract String getTitle();

}

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


答案 1

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

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

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

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

下面是一个示例来说明:

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

这里,当构造函数调用 时,尚未完成 初始化 ,并且该方法得到错误的值。这几乎肯定会导致错误和错误。BaseoverrideMeChildfinal int x

相关问题

另请参见


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

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

以下是 Effective Java 2nd Edition 第 2 项中的一段话:在面对许多构造函数参数时,请考虑生成器模式

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

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

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

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

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

但是,您当前不能仅设置 and ,而将保留为默认值。你可以提供更多的构造函数重载,但显然,随着参数数量的增长,这个数字会爆炸,你甚至可能有多个参数和参数,这真的会让事情变得一团糟。nameisAdjustablelevelsbooleanint

正如你所看到的,这不是一个令人愉快的写作模式,甚至使用起来更不愉快(这里的“真实”是什么意思?13是什么?

Bloch 建议使用生成器模式,这样可以让你编写如下内容:

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

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

另请参见

相关问题


答案 2

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

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(); 
    }
}

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

Constructing A
Using C
Constructing C

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