从父类ctor调用重写的方法

Vai*_*pai 21 c# c++ java polymorphism inheritance

我尝试从父类的构造函数中调用重写方法,并注意到跨语言的不同行为.

C++- 回声A.foo()

class A{

public: 

    A(){foo();}

    virtual void foo(){cout<<"A.foo()";}
};

class B : public A{

public:

    B(){}

    void foo(){cout<<"B.foo()";}
};

int main(){

    B *b = new B(); 
}
Run Code Online (Sandbox Code Playgroud)

Java- 回声B.foo()

class A{

    public A(){foo();}

    public void foo(){System.out.println("A.foo()");}
}

class B extends A{  

    public void foo(){System.out.println("B.foo()");}
}

class Demo{

    public static void main(String args[]){
        B b = new B();
    }
}
Run Code Online (Sandbox Code Playgroud)

C#- 回声B.foo()

class A{

    public A(){foo();}

    public virtual void foo(){Console.WriteLine("A.foo()");}
}

class B : A{    

    public override void foo(){Console.WriteLine("B.foo()");}
}


class MainClass
{
    public static void Main (string[] args)
    {
        B b = new B();              
    }
}
Run Code Online (Sandbox Code Playgroud)

我意识到在C++对象是从层次结构中最顶层的父对象创建的,所以当构造函数调用重写方法时,B甚至不存在,因此它调用方法的A'版本.但是,我不确定为什么我在Java和C#中获得不同的行为(来自C++)

Tho*_*mas 26

在C++中,正如您所正确指出的那样,对象是类型的,A直到A构造函数完成.该对象实际上在构造期间改变了类型.这就是使用类的vtable的原因A,因此A::foo()调用而不是B::foo().

在Java和C#中,即使在构造基类期间,也始终使用最派生类型的vtable(或等效机制).所以在这些语言中,B.foo()被调用.

请注意,通常不建议从构造函数中调用虚方法.如果你不是很小心,虚拟方法可能会认为对象是完全构造的,即使不是这种情况.在Java中,每个方法都隐式虚拟,您别无选择.

  • 您可以在Java中将方法标记为"final",这可以防止它们被子类覆盖.我相信C#中有一个类似的机制(也许是'密封'?). (4认同)

pol*_*nts 9

虽然我知道您正在进行实验,但重要的是要注意以下有效Java第2版,第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"
    }
}
Run Code Online (Sandbox Code Playgroud)

这里,当Base构造函数调用时overrideMe,Child尚未完成初始化final int x,并且该方法获取错误的值.这几乎肯定会导致错误和错误.