题目
最后打印什么内容
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;解析流程
这行代码执行的过程如下:
-
编译时:Java源代码被编译成字节码(.class文件)。在字节码中,成员变量x会被存储在类的常量池中,并且会被分配一个索引。
-
类加载时:当类被加载到JVM中时,成员变量x的内存分配发生在堆内存的对象空间。JVM会为每个x的类型(在这个例子中是int)分配足够的内存空间。对于int类型,通常是4个字节。
-
对象创建时:当使用new关键字创建类的实例时,JVM会为新对象分配内存空间,成员变量x的值会被初始化为int类型的默认值,即0。
-
变量赋值时:在对象创建之后,成员变量x的赋值操作会在 构造方法中执行,或者在对象创建时紧跟在new操作之后执行。在你的代码示例中,x被直接初始化为10,所以这个赋值操作是构造对象时的第一步。
-
对象使用时:一旦对象被创建并且成员变量x被赋值,你就可以在类的其他方法中访问和修改x的值。