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

Java 什么';构造函数中的可重写方法调用有什么问题?,java,oop,inheritance,constructor,overriding,Java,Oop,Inheritance,Constructor,Overriding,我有一个Wicket page类,它根据抽象方法的结果设置页面标题 public abstract class BasicPage extends WebPage { public BasicPage() { add(new Label("title", getTitle())); } protected abstract String getTitle(); } NetBeans用消息“构造函数中可重写的方法调用”警告我,但它有什么错呢?我能想象

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

public abstract class BasicPage extends WebPage {

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

    protected abstract String getTitle();

}

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

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


看看这个示例链接

调用构造函数中的可重写方法允许子类破坏代码,因此您不能保证它还能工作。这就是为什么你会收到警告

在您的示例中,如果子类重写
getTitle()
并返回null,会发生什么情况

若要“修复”此问题,可以使用一个而不是构造函数,这是对象实例关联的一种常见模式。

关于从构造函数调用可重写方法 简单地说,这是错误的,因为它不必要地为许多bug打开了可能性。调用
@Override
时,对象的状态可能不一致和/或不完整

引用有效Java第二版第17项:继承的设计和文档,否则禁止:

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

下面是一个例子来说明:

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
时,
子项
尚未完成初始化
最终整数x
,并且该方法获取了错误的值。这几乎肯定会导致bug和错误

相关问题
另见

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

这是一个来自java第二版的引用,项目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);
但是,您当前无法仅设置
名称
可调整
,并将
级别保留为默认值。您可以提供更多的构造函数重载,但很明显,随着参数数量的增加,这个数量会急剧增加,甚至可能有多个
boolean
int
参数,这会让事情变得一团糟

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

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

Telescope telly = new Telescope.Builder("X/1999").setAdjustable(true).build();
请注意,现在这些参数已命名,您可以按任意顺序设置它们,并且可以跳过希望保留为默认值的参数。这当然比伸缩构造函数好得多,特别是当有大量属于许多相同类型的参数时

另见
  • < LI> java第二版,第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
      

      看到了吗
      foo()
      在运行C的构造函数之前使用C。如果
      foo()
      要求C具有已定义的状态(即构造函数已完成),那么它将在C中遇到未定义的状态,可能会出现问题。由于您不知道被覆盖的
      foo()
      需要什么,因此会收到一条警告。

      下面的示例揭示了在超级构造函数中调用可覆盖方法时可能出现的逻辑问题

      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();
      }
      
      结果实际上是:

      每周平均收入:0

      maxWeeklySalary:0

      这是因为类B的构造函数首先调用类A的构造函数,在类A中执行B中的可重写方法。但是在方法内部,我们使用的是实例变量factor,它尚未初始化(因为A的构造函数尚未完成),因此factor是0而不是1,肯定不是2(程序员可能会认为它是)。想象一下,如果计算逻辑扭曲了十倍,跟踪错误会有多困难


      我希望这会对某人有所帮助。

      我想对于Wicket来说,最好在
      onInitialize()
      中调用
      add
      方法(请参阅):


      在Wicket的具体案例中:这就是我问Wicket的原因 开发人员在构建组件的框架生命周期中添加对显式两阶段组件初始化过程的支持,即

    • 构造-通过构造函数
    • 初始化-通过OnInitialize(在构造之后,当虚拟方法工作时!)
    • 关于wh的争论相当活跃
      public abstract class BasicPage extends WebPage {
      
          public BasicPage() {
          }
      
          @Override
          public void onInitialize() {
              add(new Label("title", getTitle()));
          }
      
          protected abstract String getTitle();
      }