为什么在Java中从构造函数中调用方法被认为是不好的做法?

为什么在Java中从构造函数中调用方法被认为是不好的做法?,java,Java,在Java中,为什么从构造函数中调用方法被认为是不好的做法?如果该方法计算量很大,是否特别糟糕?在构造函数中调用实例方法是危险的,因为对象尚未完全初始化(这主要适用于无法重写的方法)。另外,构造函数中的复杂处理也会对测试能力产生负面影响 在这样做时要小心,使用可重写的方法是不好的做法。构造函数应该只调用私有、静态或最终的方法。这有助于消除覆盖时可能出现的问题 此外,构造函数不应该启动线程。在构造函数(或静态初始值设定项)中启动线程有两个问题: 在非final类中,它增加了子类出现问题的危险 它

在Java中,为什么从构造函数中调用方法被认为是不好的做法?如果该方法计算量很大,是否特别糟糕?

在构造函数中调用实例方法是危险的,因为对象尚未完全初始化(这主要适用于无法重写的方法)。另外,构造函数中的复杂处理也会对测试能力产生负面影响


在这样做时要小心,使用可重写的方法是不好的做法。

构造函数应该只调用私有、静态或最终的方法。这有助于消除覆盖时可能出现的问题

此外,构造函数不应该启动线程。在构造函数(或静态初始值设定项)中启动线程有两个问题:

  • 在非final类中,它增加了子类出现问题的危险
  • 它打开了一扇门,允许此引用逃逸构造函数

在构造函数(或静态初始值设定项)中创建线程对象没有什么错——只是不要在那里启动它。

首先,通常在构造函数中调用方法没有问题。这些问题具体涉及调用构造函数类的可重写方法,以及将对象的
引用传递给其他对象的方法(包括构造函数)的特定情况

避免重写方法和“泄漏
this
”的原因可能很复杂,但它们基本上都与防止使用未完全初始化的对象有关

避免调用可重写的方法 避免在构造函数中调用可重写方法的原因是Java语言规范(JLS)中定义的实例创建过程的结果

除其他事项外,§12.5的过程确保在实例化派生类[1]时,其基类的初始化(即将其成员设置为初始值并执行其构造函数)发生在其自身初始化之前。这旨在通过两个关键原则实现类的一致初始化:

  • 每个类的初始化可以集中于只初始化它显式声明自己的成员,因为从基类继承的所有其他成员都已初始化
  • 每个类的初始化都可以安全地使用其基类的成员作为其自身成员初始化的输入,因为可以保证在类初始化发生时这些成员已正确初始化
  • 然而,有一个问题:Java允许在构造函数中动态分派[2]。这意味着,如果作为派生类实例化的一部分执行的基类构造函数调用派生类中存在的方法,则会在该派生类的上下文中调用该方法

    所有这些的直接结果是,当实例化派生类时,在初始化派生类之前调用基类构造函数。如果该构造函数调用被派生类重写的方法,则调用的是派生类方法(不是基类方法),即使派生类尚未初始化。显然,如果该方法使用派生类的任何成员,这是一个问题,因为它们尚未初始化

    显然,问题是基类构造函数调用可被派生类重写的方法的结果。为了防止这个问题,构造函数应该只调用自己类的final、static或private方法,因为这些方法不能被派生类重写。最终类的构造函数可以调用它们的任何方法,因为(根据定义)它们不能从中派生

    JLS的功能很好地说明了这一问题:

    class Super {
        Super() { printThree(); }
        void printThree() { System.out.println("three"); }
    }
    class Test extends Super {
        int three = (int)Math.PI;  // That is, 3
        void printThree() { System.out.println(three); }
    
        public static void main(String[] args) {
            Test t = new Test();
            t.printThree();
        }
    }
    
    此程序先打印
    0
    ,然后打印
    3
    。本例中的事件顺序如下所示:

  • newtest()
    main()
    方法中调用
  • 由于
    Test
    没有显式构造函数,因此调用其超类(即
    Super()
    )的默认构造函数
  • Super()
    构造函数调用
    printThree()
    。这被分派到
    Test
    类中方法的重写版本
  • Test
    类的
    printThree()
    方法打印
    three
    成员变量的当前值,这是默认值
    0
    (因为
    Test
    实例尚未初始化)
  • printThree()
    方法和
    Super()
    构造函数每次退出,然后初始化
    Test
    实例(此时
    three
    设置为
    3
  • main()
    方法再次调用
    printThree()
    ,这一次打印
    3
    的预期值(因为
    Test
    实例现在已经初始化)
  • 如上所述,§12.5规定(2)必须发生在(5)之前,以确保在进行
    Test
    之前对
    Super
    进行初始化。但是,动态分派意味着(3)中的方法调用是在未初始化的
    Test
    类的上下文中运行的,从而导致意外行为

    避免泄漏
    
    禁止将
    这个
    从构造函数传递到另一个对象的限制更容易解释

    基本上,一个对象在其构造函数完成执行之前不能被认为是完全初始化的(因为它的目的是完成对象的初始化)。因此,如果构造函数将对象的
    this
    传递给另一个对象,则该另一个对象将具有对该对象的引用,即使该对象尚未完全初始化(