面试题答案
一键面试- 方法调用过程
- 解析调用:在编译期,Java编译器会对方法调用进行解析,确定方法的符号引用。对于静态方法、私有方法、构造方法和final方法,这些方法在编译期就能确定具体的调用版本,采用静态绑定。例如,一个类的静态方法
public static void staticMethod() {}
,通过类名直接调用ClassName.staticMethod()
,在编译时就确定了调用的是这个静态方法。 - 分派调用:对于非上述特殊类型的方法,Java采用动态绑定实现运行时多态。
- 静态分派:方法重载(Overloading)基于静态分派。例如,有一个方法
public void print(int num) {}
和public void print(String str) {}
,Animal animal = new Dog();
,当调用animal.print(10);
时,编译器根据参数的静态类型(这里是int
)在编译期就确定调用哪个print
方法,这就是静态分派。 - 动态分派:方法重写(Overriding)基于动态分派。以
Animal
类及其子类Dog
为例,Animal
类有方法public void makeSound() {}
,Dog
类重写此方法public void makeSound() {System.out.println("Woof!");}
。Animal animal = new Dog();
,当调用animal.makeSound();
时,实际执行的是Dog
类的makeSound
方法,这就是动态分派。
- 静态分派:方法重载(Overloading)基于静态分派。例如,有一个方法
- 解析调用:在编译期,Java编译器会对方法调用进行解析,确定方法的符号引用。对于静态方法、私有方法、构造方法和final方法,这些方法在编译期就能确定具体的调用版本,采用静态绑定。例如,一个类的静态方法
- JVM层面实现动态绑定
- 虚方法表(Virtual Method Table):JVM为每个类都维护一个虚方法表。虚方法表中存放着每个虚方法(可被重写的方法)在实际运行时的入口地址。当类加载到JVM中时,JVM会根据类的继承关系,为每个类构建虚方法表。
- 子类的虚方法表会继承父类的虚方法表,并根据自身对方法的重写情况进行调整。如果子类重写了父类的某个虚方法,那么子类虚方法表中该方法的入口地址会指向子类重写的方法实现。例如,
Dog
类继承Animal
类并重写makeSound
方法,Dog
类的虚方法表中makeSound
方法的入口地址就指向Dog
类中makeSound
方法的实际实现代码。
- 子类的虚方法表会继承父类的虚方法表,并根据自身对方法的重写情况进行调整。如果子类重写了父类的某个虚方法,那么子类虚方法表中该方法的入口地址会指向子类重写的方法实现。例如,
- 动态绑定过程:在运行时,当JVM执行到一个虚方法调用指令(如
invokevirtual
)时,JVM首先根据对象的实际类型(而不是引用类型)找到对应的虚方法表。然后,在虚方法表中查找与所调用方法签名匹配的方法入口地址,并调用该方法。例如,Animal animal = new Dog();
,当执行animal.makeSound();
时,JVM根据animal
实际指向的Dog
对象,找到Dog
类的虚方法表,在其中找到makeSound
方法的入口地址并执行该方法,从而实现运行时多态的动态绑定。
- 虚方法表(Virtual Method Table):JVM为每个类都维护一个虚方法表。虚方法表中存放着每个虚方法(可被重写的方法)在实际运行时的入口地址。当类加载到JVM中时,JVM会根据类的继承关系,为每个类构建虚方法表。