Java中的虚拟表和摘要 [英] Virtual tables and abstract in Java

查看:188
本文介绍了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 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 to 1.
                • invokes Derived.foo()
                  • prints Derived.x, which still has the default value of 0

                  • 打印 Derived.x ,现在 2

                  • prints Derived.x, which is now 2.

                  要完全了解发生了什么,您需要了解一些事项。

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

                  基本 x 派生 x 是完全不同的字段,恰好具有相同的名称。 Derived.foo 打印 Derived.x ,而不是 Base.x ,因为后者被前者遮蔽。

                  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.

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



                  初始化程序块和字段定义



                  初始化程序块以声明顺序组合,形成一个插入所有构造函数的大块代码。具体来说,它是在 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 and Derived. If we decompiled their constructors, we would see something like

                  public 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 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.

                  这篇关于Java中的虚拟表和摘要的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆