【问题标题】:Simulate constructor overriding in Java?在Java中模拟构造函数覆盖?
【发布时间】:2016-09-13 08:11:38
【问题描述】:

有几次我认为在 Java 中拥有可覆盖的构造函数会很好。

“可覆盖”表示构造逻辑可以在降级类中被覆盖和/或扩展,就像可以覆盖普通方法一样,即能够在子类之后调用父方法。

这个任务可以被表述为有一个方法,比如说,称为init(),它在构造时被调用,但只能在堆栈的最后一个构造函数中调用。

喜欢:

public class InitializationOverride {

   public static class A {
      A() {
         System.out.println("Constructor of A");
      }

      void init() {
         System.out.println("Init of A");
      }
   }

   public static class B extends A {

      B() {
         System.out.println("Constructor of B");
      }

      @Override
      void init() {
         System.out.println("Init of B");
      }
   }

   public static class C extends B {

      C() {
         System.out.println("Constructor of C");
      }

      @Override
      void init() {
         System.out.println("Init of C");
      }
   }

   public static void main(String[] args) {

      new A(); // should print "Constructor of A, Init of A"
      new B(); // should print "Constructor of A, Constructor of B, Init of B"
      new C(); // should print "Constructor of A, Constructor of B, Constructor of C, Init of C"

   }
}

明显的方法是写

public static void main(String[] args) {

      new A().init();
      new B().init();
      new C().init();

   }

但这并不能保证init() 不会忘记调用。

有没有可能做点什么?

更新

在设计时不知道哪个类将是“最后一个”。预计将来会开发该类树。

更新 2

下面是反射和构造函数代码要求的解决方案,最后调用currentStage()

public class InitializationOverride {

   public static class A {
      A() {
         System.out.println("Constructor of A");

         currentStage(A.class);
      }

      void currentStage(Class<?> cls) {
         if( cls == getClass() ) {
            init();
         }
      }

      void init() {
         System.out.println("Init of A");
      }
   }

   public static class B extends A {

      B() {
         System.out.println("Constructor of B");

         currentStage(B.class);
      }

      @Override
      void init() {
         System.out.println("Init of B");
      }
   }

   public static class C extends B {

      C() {
         System.out.println("Constructor of C");

         currentStage(C.class);
      }

      @Override
      void init() {
         System.out.println("Init of C");
      }
   }

   public static void main(String[] args) {

      new A(); // should print "Constructor of A, Init of A"
      new B(); // should print "Constructor of A, Constructor of B, Init of B"
      new C(); // should print "Constructor of A, Constructor of B, Constructor of C, Init of C"

   }

可以写得更简单吗?

【问题讨论】:

  • 你没有说明为什么你认为这些方法应该被构造函数调用。
  • C 构造函数中直接调用init 有什么问题?将super.init(); 放入每个覆盖方法有什么问题?
  • @defaultlocale 如果我将init() 放入C 构造函数中,那么在构造AB 时将不会调用它。将super.init() 放在任何地方都没有问题,但这有什么帮助呢?
  • 在构造函数中调用的覆盖方法是可能的,但它被认为是一个糟糕的设计,可能会导致一些问题,请参阅此问题以了解详细信息whats-wrong-with-overridable-method-calls-in-constructors
  • 现在变得越来越复杂了。你能告诉我们这个设计背后的原因吗?此外,这个问题包含几个建议:stackoverflow.com/questions/27453105/…

标签: java inheritance constructor initialization


【解决方案1】:

构造函数不应调用可覆盖的方法。如果需要调用此类方法,更好的解决方案是保护构造函数并提供静态工厂方法:

public class InitializationOverride {

   public static class A {
      protected A() {
         System.out.println("Constructor of A");
      }

      public static A newInstance(){
        A a = new A();
        a.init();
        return a;
      }

      protected void init() {
         System.out.println("Init of A");
      }
   }

   public static class B extends A {

      protected B() {
         System.out.println("Constructor of B");
      }

      public static B newInstance(){
        B b = new B();
        b.init();
        return b;
      }

      @Override
      protected void init() {
         System.out.println("Init of B");
      }
   }

   public static class C extends B {

      protected C() {
         System.out.println("Constructor of C");
      }

      public static C newInstance(){
        C c = new C();
        c.init();
        return c;
      }

      @Override
      protected void init() {
         System.out.println("Init of C");
      }
   }

   public static void main(String[] args) {

      A.newInstance(); // should print "Constructor of A, Init of A"
      B.newInstance(); // should print "Constructor of A, Constructor of B, Init of B"
      C.newInstance(); // should print "Constructor of A, Constructor of B, Constructor of C, Init of C"

   }
}

编辑 更多解释:这种解决方案提供了好处,但也有缺点。您应该为类(即在 Javadoc 中)提供一个合同,扩展您的类的子类应该遵循这个对象创建标准。它还创建了更多代码。好处是对象以这种方式创建:

C obj = C.newInstance() 

...总是完全初始化,不需要记住调用 init() 方法显式。

请记住,它也是在类的包外创建对象的唯一方法(构造函数不可用),但在同一个包内的构造函数仍然可用(受保护的方法在同一个包内可用)

【讨论】:

    【解决方案2】:

    在Java中,当子class被实例化时,总是调用父类的默认constructor(除非指定了任何其他constructor)。现在,如果您需要有一个公共代码需要为所有classes 执行,建议将它放在constructor 中。但是,如果您希望仅在层次结构中的最后一个 class 中执行某些操作,那么 (a) 您可以将其写入最后一个 constructor 本身或 (b) 编写一个初始化块,下面的示例演示了这一点:

    public class Test extends Test2{
    
        public Test(){
            System.out.println("In test");
            System.out.println("Init last");
        }
    
        {
            System.out.println("Init");
        }
    
        public static void main(String[] args) {
            new Test();
        }
    }
    
    class Test2{
        public Test2(){
            System.out.println("In test 2");
        }
    }
    

    【讨论】:

      【解决方案3】:

      像这样改变你的类构造函数,每个对象的init方法都会调用this.init(),你只需要改变最上层的构造函数。因为在创建对象时父类构造函数肯定会调用

      public class Test {
          public static class A {
      
              public A() {
      
                  this.init();
              }
      
              void init() {
                  System.out.println("Called in A");
              }
          }
      
          public static class B extends A {
      
              @Override
              void init() {
                  System.out.println("Called in B");
              }
          }
      
          public static class C extends B {
      
              @Override
              void init() {
                  System.out.println("Called in C");
              }
          }
      
          public static void main(String[] args) {
      
              new A(); // should "Called in A" printed
              new B(); // should "Called in B" printed
              new C(); // should "Called in C" printed
      
          }
      }
      

      【讨论】:

      • 你能写出整个例子吗?我不明白,为什么它会起作用?
      • 对不起,你是对的,我的代码是个坏例子。请查看我的更新。
      • 根据 Joshua 的 Bloch “Effective Java”中的第 17 项,您的示例设计不佳:类必须遵守更多限制才能允许继承。构造函数不得直接或间接调用可覆盖的方法。如果您违反此规则,将导致程序失败。超类构造函数在子类构造函数之前运行,因此子类中的覆盖方法将在子类构造函数运行之前被调用。如果覆盖方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行。
      • 如果你还没有决定哪个类是顶级父类,那么就很难做到这一点
      • jakub,我同意你的观点'构造函数不能调用可覆盖的方法',但是 OP 已经在所有类中声明了公共方法,所以我没有想过改变他的所有设计
      【解决方案4】:

      为此使用 super.init() 调用根父类 init()。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-08-31
        • 2014-06-09
        • 2018-03-15
        • 2019-11-24
        • 2017-02-03
        • 1970-01-01
        • 2014-04-11
        • 1970-01-01
        相关资源
        最近更新 更多