java 虚拟表,Java中的虚拟表和摘要

本文解析了Java中抽象基类与子类构造函数执行顺序,重点讨论了基类构造函数先于子类执行导致的`Base.x`被`Derived.x`覆盖的情况。通过实例和虚拟方法调用原理揭示了为何`Derived`构造函数两次打印不同值的过程。
摘要由CSDN通过智能技术生成

In an interview I was given the following code:

public abstract class Base {

public int x = 1;

public Base() {

foo();

}

public abstract void foo();

}

public class Derived extends Base {

int x = 2;

@Override

public void foo() {

System.out.println("Derived: "+x);

}

}

class Main {

public static void main(String... args) {

Base base = new Derived();

base.foo();

}

}

They asked:

What will be printed?

If we were using C++ I think the code should give a compilation error because when the Derived constructor is called first the constructor of the Base class is called. At this point the foo method doesn't exist.

In addition I know that first the inherited class constructor is called, before all the

variables is created.

However in Java we get:

Derived: 0

Derived: 2

Why?

I know that like in C++ Java inheritance is based always on virtual tables,

and the constructor of the Base class is called before the constructor of the Derived class.

解决方案

This is the order in which the code is executed. More details follow.

main()

invokes Derived.() (the implicit nullary constructor)

invokes Base.()

sets Base.x to 1.

invokes Derived.foo()

prints Derived.x, which still has the default value of 0

sets Derived.x to 2.

invokes Derived.foo().

prints Derived.x, which is now 2.

To completely understand what is going on, there are several things you need to know.

Field Shadowing

Base's x and Derived's x are completely different fields which happen to have the same name. Derived.foo prints Derived.x, not Base.x, since the latter is "shadowed" by the former.

Implicit Constructors

Since Derived has no explicit constructor, the compiler generates an implicit zero-argument constructor. In Java, every constructor must call one superclass constructor (with the exception of Object, which has no superclass), which gives the superclass a chance to safely initialize its fields. A compiler-generated nullary constructor simply calls the nullary constructor of its superclass. (If the superclass has no nullary constructor, a compilation error is produced.)

So, Derived's implicit constructor looks like

public Derived() {

super();

}

Initializer Blocks and Field Definitions

Initializer blocks are combined in declaration order to form a big block of code which is inserted into all constructors. Specifically, it is inserted after the super() call but before the rest of the constructor. Initial value assignments in field definitions are treated just like initializer blocks.

So if we have

class Test {

{x=1;}

int x = 2;

{x=3;}

Test() {

x = 0;

}

}

This is equivalent to

class Test {

int x;

{

x = 1;

x = 2;

x = 3;

}

Test() {

x = 0;

}

}

And this is what the compiled constructor will actually look like:

Test() {

// implicit call to the superclass constructor, Object.()

super();

// initializer blocks, in declaration order

x = 1

x = 2

x = 3

// the explicit constructor code

x = 0

}

Now let's return to Base and Derived. If we decompiled their constructors, we would see something like

public Base() {

super(); // Object.()

x = 1; // assigns Base.x

foo();

}

public Derived() {

super(); // Base.()

x = 2; // assigns Derived.x

}

Virtual Invocations

In Java, invocations of instance methods normally go through virtual method tables. (There are exceptions to this. Constructors, private methods, final methods, and methods of final classes cannot be overridden, so these methods can be invoked without going through a vtable. And super calls do not go through vtables, since they are inherently not polymorphic.)

Every object holds a pointer to a class handle, which contains a vtable. This pointer is set as soon as the object is allocated (with NEW) and before any constructors are called. So in Java, it is safe for constructors to make virtual method calls, and they will be properly directed to the target's implementation of the virtual method.

So when Base's constructor calls foo(), it invokes Derived.foo, which prints Derived.x. But Derived.x hasn't been assigned yet, so the default value of 0 is read and printed.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值