【JVM实战理解-面试题一】

题目

最后打印什么内容

Father.java

public class Father {

    public int x = 10;

    public Father(){
        print();
        x = 30;
    }

    public void print(){
        System.out.println("father:"+x);
    }
}

Son.java

public class Son extends Father {
    public int x = 20;

    public Son()
    {
        print();
        x = 40;
    }

    public void print()
    {
        System.out.println("son:"+x);
    }

    public static void main(String[] args) {
        Father f = new Son();
        System.out.println(f.x);
    }
}

结果

son:0
son:20
30

解析

前置知识点

  • 静态类型与动态类型

    • 静态类型(编译时类型):这是在编写代码时已知的类型,它在编译期间就已经确定。静态类型决定了哪些属性和方法是可见的,以及它们如何被访问。在Java中,静态类型通常用于决定访问控制(如访问权限)和静态成员。

    • 动态类型(运行时类型):这是在程序运行时才能确定的类型。在Java中,动态类型主要用于运行时多态,即方法的覆盖(Override)。在调用覆盖的方法时,JVM会根据对象的实际类型来决定调用哪个版本的方法。

  • 属性隐藏与静态类型

    • 当子类隐藏了父类的属性时,通过子类对象访问这个属性,总是会访问子类中定义的版本。这是因为属性的访问是基于对象的静态类型,即编译时确定的类型。编译器在编译代码时,根据对象的静态类型来决定访问哪个属性。

解析打印结果

  • son:0
    当调用new Son();时,会先调用父类的构造方法,根据【JVM基础知识及实战项目思考和总结】 有说过类加载流程,和类加载条件。当执行Father构造器的print();一行时,根据多态是执行的子类的print方法,此时子类并没有执行new的流程,所以子类x的值目前是默认值0。
  • son:20
    当父类构造方法执行完成后,开始执行子类的构造方法,此时x已经属性已经初始化完成,调用print方法时,x应该是20
  • 30
    最后调用f.x 时,是调用父类的属性,上面知识点已经说了,属性和方法的覆盖和隐藏关系,所以当调用父类引用的属性时,最后就是父类的x值为30。

附加public int x = 10;解析流程

这行代码执行的过程如下:

  1. 编译时:Java源代码被编译成字节码(.class文件)。在字节码中,成员变量x会被存储在类的常量池中,并且会被分配一个索引。

  2. 类加载时:当类被加载到JVM中时,成员变量x的内存分配发生在堆内存的对象空间。JVM会为每个x的类型(在这个例子中是int)分配足够的内存空间。对于int类型,通常是4个字节。

  3. 对象创建时:当使用new关键字创建类的实例时,JVM会为新对象分配内存空间,成员变量x的值会被初始化为int类型的默认值,即0。

  4. 变量赋值时:在对象创建之后,成员变量x的赋值操作会在 构造方法中执行,或者在对象创建时紧跟在new操作之后执行。在你的代码示例中,x被直接初始化为10,所以这个赋值操作是构造对象时的第一步。

  5. 对象使用时:一旦对象被创建并且成员变量x被赋值,你就可以在类的其他方法中访问和修改x的值。

上一篇:【C++】map 和 set(二叉搜索树、AVL树、红黑树)


下一篇:尚硅谷redis 第98-120节答疑 深入理解 Redis 与 MySQL 的连接与性能优化