Java中的虚拟表和摘要 [英] Virtual tables and abstract in Java
问题描述
在一次采访中,我获得了以下代码:
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();
}
}
他们问:
将打印什么?
What will be printed?
如果我们使用的是C ++,我认为代码应该给出编译错误,因为首先调用 Derived
构造函数时,会调用 Base
类的构造函数。此时 foo
方法不存在。
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.
然而在Java中我们得到:
However in Java we get:
Derived: 0
Derived: 2
为什么?
我知道在C ++中,Java继承始终基于虚拟表,
和 Base $ c的构造函数在$ code> Derived
类的构造函数之前调用$ c> class。
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()
- 调用
派生。< init>()
(隐式无效构造函数)
- 调用
Base。< init>()
- 设置
Base.x
到1
。 - 调用
Derived.foo()
- 打印
Derived.x
,其默认值仍为0
main()
- invokes
Derived.<init>()
(the implicit nullary constructor)- invokes
Base.<init>()
- sets
Base.x
to1
. - invokes
Derived.foo()
- prints
Derived.x
, which still has the default value of0
- 打印
Derived.x
,现在2
。
- prints
Derived.x
, which is now2
.
要完全了解发生了什么,您需要了解一些事项。
To completely understand what is going on, there are several things you need to know.
基本
的x
和派生
的x
是完全不同的字段,恰好具有相同的名称。Derived.foo
打印Derived.x
,而不是Base.x
,因为后者被前者遮蔽。Base
'sx
andDerived
'sx
are completely different fields which happen to have the same name.Derived.foo
printsDerived.x
, notBase.x
, since the latter is "shadowed" by the former.自
Derived
没有显式构造函数,编译器生成隐式零参数构造函数。在Java中,每个构造函数都必须调用一个超类构造函数(Object
除外,它没有超类),这使超类有机会安全地初始化其字段。编译器生成的nullary构造函数只是调用其超类的nullary构造函数。 (如果超类没有nullary构造函数,则会产生编译错误。)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 ofObject
, 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 likepublic Derived() { super(); }
初始化程序块和字段定义
初始化程序块以声明顺序组合,形成一个插入所有构造函数的大块代码。具体来说,它是在
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.因此,如果我们有
class Test { {x=1;} int x = 2; {x=3;} Test() { x = 0; } }
这相当于
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.<init>() super(); // initializer blocks, in declaration order x = 1 x = 2 x = 3 // the explicit constructor code x = 0 }
现在让我们回到
Base
和派生
。如果我们反编译他们的构造函数,我们会看到类似Now let's return to
Base
andDerived
. If we decompiled their constructors, we would see something likepublic Base() { super(); // Object.<init>() x = 1; // assigns Base.x foo(); } public Derived() { super(); // Base.<init>() x = 2; // assigns Derived.x }
虚拟调用
在Java中,实例方法的调用通常会通过虚方法表。 (也有例外。构造函数,私有方法,final方法和final类的方法都不能被覆盖,因此可以在不通过vtable的情况下调用这些方法。并且
super
调用不通过vtable,因为它们本质上不是多态的。)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.)每个对象都有一个指向类句柄的指针,该句柄包含一个vtable。一旦分配了对象(使用
NEW
)并且在调用任何构造函数之前,就会设置此指针。所以在Java中,构造函数可以安全地进行虚方法调用,并且它们将被正确地定向到目标的虚方法实现。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.所以当<$ c时$ c> Base 的构造函数调用
foo()
,它调用Derived.foo
,打印Derived.x
。但Derived.x
尚未分配,因此读取并打印默认值0
。So when
Base
's constructor callsfoo()
, it invokesDerived.foo
, which printsDerived.x
. ButDerived.x
hasn't been assigned yet, so the default value of0
is read and printed.这篇关于Java中的虚拟表和摘要的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- prints
- sets
- invokes
- invokes
- 打印
- 设置
- 调用
- 调用