【问题标题】:Using abstract init() function in abstract class's constructor在抽象类的构造函数中使用抽象 init() 函数
【发布时间】:2011-03-21 13:03:26
【问题描述】:

我有这样的事情:

    public abstract class Menu {
     public Menu() {
      init();
     }

     protected abstract void init();

     protected void addMenuItem(MenuItem menuItem) {
      // some code...
     }
    }

    public class ConcreteMenu extends Menu {
     protected void init() {
      addMenuItem(new MenuItem("ITEM1"));
      addMenuItem(new MenuItem("ITEM2"));
      // ....
     }
    }

//Somewhere in code
Menu menu1 = new ConcreteMenu();

如你所见,超类的init方法是抽象的,在对象创建后被构造函数自动调用。

我很好奇,当我需要创建一些结构不会及时更改的此类对象时,我是否会遇到这样的代码问题。

会有更好的方法吗?它可以在 Java 中运行,但它可以在 C++ 和 ActionScript 中运行吗?

谢谢你的回答。

【问题讨论】:

    标签: java


    【解决方案1】:

    不要从构造函数中调用可覆盖的方法。

    引自Effective Java 2nd Edition,Item 17: Design and document for inheritance,或者禁止它

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

    这里有一个例子来说明:

    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的初始化,方法得到了错误的值。这几乎肯定会导致错误和错误。

    相关问题

    另见

    【讨论】:

    • 我关于Java构造函数和模板方法模式的相关文章:novyden.blogspot.com/2011/08/…
    • 我理解构造函数不能调用任何虚方法其合约未指定它们必须可以从构造函数调用。让构造函数调用一个虚方法,其契约规定它不能依赖子类构造函数将设置的任何字段,这有什么问题吗?
    • 您在面向对象编程类中学到的第一件事就是执行顺序(构造函数调用其超类的构造函数,然后执行构造函数体的其余部分)。实际上,在您的示例中,您没有考虑到这一点。然而,在 jarek 提供的代码中,情况并非如此。他将其用作超类构造函数的扩展。问题更多:只要在init函数中不使用子类构造函数参数,有什么反对使用this的吗?
    • @polygenelubricants @ jarek 引用:“如果重写方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行。”因此,只有当覆盖方法依赖于子类构造函数执行的初始化时,它才是一个问题。提供的代码不依赖于此,因此可以使用。不过,这仍然是一个不好的编码习惯:如果代码仅供您自己使用,这不是问题,因为只有您自己会覆盖父类。但是,如果您正在创建 API,使用它的人可能会遇到麻烦。
    【解决方案2】:

    你是对的,它可能会导致派生类出现问题,派生类的实例变量在构造函数中或在创建实例时初始化。 如果你有这个:

    public class ConcreteMenu extends Menu {
     String firstItem = "Item1";
    
     protected void init() {
      addMenuItem(new MenuItem(firstItem));
      // ....
     }
    }
    

    然后 MenuItem 将有 null 作为它的构造函数参数!

    在构造函数中调用非最终方法是一种冒险的做法。

    一个简单的解决方案可能是将构造和初始化分开,如下所示:

    Menu menu = new ConcreteMenu();
    menu.init();
    

    【讨论】:

      【解决方案3】:

      正如其他人提到的,从构造函数调用可覆盖的方法正在进入一个痛苦的世界......

      您是否考虑过在构造函数本身中进行初始化?

      public abstract class Menu { 
          public Menu() { 
              ....
          } 
      
          protected void addMenuItem(MenuItem menuItem) { 
              // some code... 
          } 
      } 
      
      public class ConcreteMenu extends Menu { 
          public ConcreteMenu() { 
              super();
              addMenuItem(new MenuItem("ITEM1")); 
              addMenuItem(new MenuItem("ITEM2")); 
              // .... 
          } 
      } 
      

      【讨论】:

      • 是的,我只是想清楚地告诉您,您必须通过覆盖 init() 方法在每个子类中创建这些菜单项,在响应之后,我可能会创建构建器类,该构建器类将首先构造对象,然后初始化它...
      猜你喜欢
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-09
      • 2015-03-20
      • 2011-08-01
      • 2021-11-24
      相关资源
      最近更新 更多