I tried calling an overridden method from a constructor of a parent class and noticed different behavior across languages.
C++
- echoes 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();
}
Java
- echoes 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();
}
}
C#
- echoes 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();
}
}
I realize that in C++ objects are created from top-most parent going down the hierarchy, so when the constructor calls the overridden method, B does not even exist, so it calls the A' version of the method. However, I am no开发者_如何学编程t sure why I am getting different behavior in Java and C# (from C++)
In C++, as you correctly noted, the object is of type A
until the A
constructor is finished. The object actually changes type during its construction. This is why the vtable of the A
class is used, so A::foo()
gets called instead of B::foo()
.
In Java and C#, the vtable (or equivalent mechanism) of the most-derived type is used throughout, even during construction of the base classes. So in these languages, B.foo()
gets called.
Note that it is generally not recommended to call a virtual method from the constructor. If you're not very careful, the virtual method might assume that the object is fully constructed, even though that is not the case. In Java, where every method is implicitly virtual, you have no choice.
While I understand that you're doing this for experiments, it's important to note the following quote from Effective Java 2nd Edition, Item 17: Design and document for inheritance, or else prohibit it:
There are a few more restrictions that a class must obey to allow inheritance. Constructors must not invoke overridable methods, directly or indirectly. If you violate this rule, program failure will result. The superclass constructor runs before the subclass constructor, so the overriding method in the subclass will be invoked before the subclass constructor has run. If the overriding method depends on any initialization performed by the subclass constructor, the method will not behave as expected.
Here's an example to illustrate:
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"
}
}
Here, when Base
constructor calls overrideMe
, Child
has not finished initializing the final int x
, and the method gets the wrong value. This will almost certainly lead to bugs and errors.
精彩评论