gpt4 book ai didi

java - Java中的虚拟表和抽象

转载 作者:IT老高 更新时间:2023-10-28 20:22:38 25 4
gpt4 key购买 nike

在一次采访中,我得到了以下代码:

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 方法不存在。

另外我知道首先调用继承的类构造函数,在所有的变量已创建。

然而在 Java 中我们得到:

Derived: 0Derived: 2

为什么?

我知道就像在 C++ 中一样,Java 继承总是基于虚拟表,并且 Base 类的构造函数在 Derived 类的构造函数之前调用。

最佳答案

这是代码的执行顺序。更多细节如下。

  • main()
    • 调用 Derived.<init>() (隐式空元构造函数)
      • 调用 Base.<init>()
        • 设置 Base.x1 .
        • 调用 Derived.foo()
          • 打印 Derived.x ,它的默认值仍然是 0
      • 设置 Derived.x2 .
    • 调用 Derived.foo() .
      • 打印 Derived.x ,现在是 2 .

要完全了解发生了什么,您需要了解几件事。

字段阴影

BasexDerivedx是完全不同的字段,它们恰好具有相同的名称。 Derived.foo打印 Derived.x ,而不是 Base.x ,因为后者被前者“遮蔽”了。

隐式构造函数

自从 Derived没有显式构造函数,编译器生成一个隐式零参数构造函数。在 Java 中,每个构造函数都必须调用一个父类(super class)构造函数(Object 除外,它没有父类(super class)),这使父类(super class)有机会安全地初始化其字段。编译器生成的空构造函数只是调用其父类(super class)的空构造函数。 (如果父类(super class)没有空构造函数,则会产生编译错误。)

所以,Derived的隐式构造函数看起来像

public Derived() {
super();
}

初始化程序 block 和字段定义

初始化程序 block 按声明顺序组合成一大块代码,插入到所有构造函数中。具体来说,它是在 super() 之后插入的。调用但在构造函数的其余部分之前。字段定义中的初始值分配被视为初始化 block 。

如果我们有

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;
}
}

这就是编译后的构造函数的实际样子:

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
}

现在让我们回到 BaseDerived .如果我们反编译它们的构造函数,我们会看到类似

public Base() {
super(); // Object.<init>()
x = 1; // assigns Base.x
foo();
}

public Derived() {
super(); // Base.<init>()
x = 2; // assigns Derived.x
}

虚拟调用

在 Java 中,实例方法的调用通常通过虚拟方法表。 (这也有异常(exception)。构造函数、私有(private)方法、final 方法和 final 类的方法不能被覆盖,因此这些方法可以在不通过 vtable 的情况下调用。并且 super 调用不通过 vtable,因为它们是本质上不是多态的。)

每个对象都有一个指向类句柄的指针,其中包含一个 vtable。一旦分配了对象(使用 NEW )并且在调用任何构造函数之前,就会设置此指针。所以在 Java 中,构造函数调用虚方法是安全的,它们会被正确地定向到目标的虚方法实现。

所以当Base的构造函数调用 foo() , 它调用 Derived.foo ,打印 Derived.x .但是Derived.x还没有赋值,所以默认值0被阅读和打印。

关于java - Java中的虚拟表和抽象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9554379/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com