【问题标题】:Is it OK to call abstract method from constructor in Java? [duplicate]从Java中的构造函数调用抽象方法可以吗? [复制]
【发布时间】:2013-02-25 23:17:15
【问题描述】:

假设我有一个实现 Runnable 接口的抽象基类。

public abstract class Base implements Runnable {

  protected int param;

  public Base(final int param) {
      System.out.println("Base constructor");
      this.param = param;
      // I'm using this param here
      new Thread(this).start();
      System.out.println("Derivative thread created with param " + param);
  }

  @Override
  abstract public void run();
}

这是几个派生类之一。

public class Derivative extends Base {

  public Derivative(final int param) {
      super(param);
  }

  @Override
  public void run() {
      System.out.println("Derivative is running with param " + param);
  }

  public static void main(String[] args) {
      Derivative thread = new Derivative(1);
  }

}

关键是我希望我的 Base 类做一些通用的东西,而不是每次都复制它。 实际上,它运行良好,输出始终相同:

基础构造函数 使用参数 1 创建的派生线程 导数使用参数 1 运行

但是在 JAVA 中启动一个线程调用构造函数中的抽象方法是否安全?因为,据我所知,在大多数情况下,在 C++ 和 C# 中它是不安全的。 谢谢!

【问题讨论】:

    标签: java methods constructor virtual abstract


    【解决方案1】:

    这段代码演示了为什么您应该从不从构造函数调用抽象方法或任何其他可覆盖的方法:

    abstract class Super {
        Super() {
            doSubStuff();
        }
        abstract void doSubStuff();
    }
    
    class Sub extends Super {
        String s = "Hello world";
    
        void doSubStuff() {
            System.out.println(s);
        }
    }
    
    public static void main(String[] args) {
        new Sub();
    }
    

    运行时,将打印null。这意味着构造函数中唯一的“安全”方法是私有和/或最终方法。

    另一方面,您的代码实际上并没有从构造函数中调用抽象方法。相反,您将未初始化的对象传递给另一个线程进行处理,这更糟糕,因为您正在启动的线程可能会被赋予优先级并在您的 Base 完成其初始化之前执行。

    【讨论】:

    • 让构造函数调用抽象或虚方法是否有任何不当之处其契约规定它可以从构造函数调用,从这样的上下文调用必须是安全的,并且可以只调用其他同样安全的方法?
    • @supercat:我想这取决于您对文档的信任程度,您对其他人遵循此类文档的信任程度,以及您对可能扩展此类或任何子类的每个人的信任程度它以适当地传播或记住提及此类警告。在我看来,这是相当大的信任。我更喜欢可以通过自动化测试证明而不能证明的东西。
    • 很公平。我想到的最大用例是派生类需要覆盖一个方法以返回一个常量,该常量对于每个子类的所有实例都必须相同,并且在构造函数和其他地方都需要,所以一个典型的实现是int getWoozleForType() { return 23;}。将这样的东西传递给构造函数并将其存储在实例字段中似乎有点恶心,而且我想不出任何其他看起来很有吸引力的方法。
    • 我不明白这是怎么失败的?我觉得很好。显然你是对的,但我无法理解如何继承抽象方法并定义它的作用可以打印null?
    • @finnrayment 问题是当Super 构造函数(在Sub 构造之前调用)尝试调用doSubStuff() 时,字符串s 还没有被初始化。
    【解决方案2】:

    这不是一个好主意,因为在调用 run() 时,衍生对象可能尚未初始化。如果 run() 依赖于 Derivative 中的任何状态,它可能会失败。

    在您的简单情况下,它可以工作。但是子类没有意义。你可以简单地

    public Base(final int param, Runnable action) {
    
      new Thread(action).start();
    

    【讨论】:

      【解决方案3】:

      从构造函数调用抽象方法是一种非常糟糕的做法。从构造函数调用的方法应该始终是私有的或最终的,以防止被覆盖。

      查看此问题的链接here

      【讨论】:

        【解决方案4】:

        this 传递出构造函数称为“让this 从构造函数中逃脱”,并且会导致一些特别讨厌和奇怪的错误,因为对象可能处于不一致的状态。

        this 被传递给另一个线程时尤其如此,如本例所示。由于 JVM 有权在线程中重新排序语句,您可能会发生未定义的行为/状态。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-10-07
          • 2011-07-10
          • 1970-01-01
          • 1970-01-01
          • 2010-09-20
          • 1970-01-01
          • 2016-05-04
          • 1970-01-01
          相关资源
          最近更新 更多