方法调用
- 方法调用
- 静态链接
- 动态链接
- 案例
- 虚方法与非虚方法
- 虚方法(Virtual Method)
- 非虚方法(Non-Virtual Method)
- 方法返回地址
方法调用
我们编写Java程序的时候,我们自己写的类通常不仅仅是调用自己本类的方法。调用别的类的方法的时候,从字节码的角度,我们调用别的类的方法,字节码里面存储的是别的类的符号引用。
但是JVM运行的时候,我们需要一个机制去把这个符号,转化成实际的引用的类方法的地址,这样我们运行的时候,才能够找到要调用的方法。
在JVM中,将符号引用转化为调用方法的直接引用与方法的绑定机制有关,方法的绑定机制有两种:
- 静态绑定
- 动态绑定
静态链接
Java源代码转化成字节码文件装载到JVM区域的时候,如果被调用的类的目标方法,编译期间就可以确定下来的话,而且运行期间不会变。这时候,我们可以将调用方法的符号引用直接转化成目标方法的直接引用,这种情况就是静态链接或者早期绑定。
动态链接
被调用的方法如果编译期间无法确定下来,这种情况,程序只能够在运行期间将调用方法的符号引用转化成直接引用,这种情况就叫动态链接或者晚期绑定。
案例
class Student{
public void study() {
System.out.println("begin study");
}
}
interface Play{
public void play();
}
class JuniorStudent extends Student implements Play{
@Override
public void play() {
System.out.println("JuniorStudent play");
}
}
class MiddleStudent extends Student implements Play{
@Override
public void play() {
System.out.println("MiddleStudent play");
}
}
public class LinkTest {
public void play(Play play) {
play.play();
}
public void study(Student student) {
student.study();
}
}
虚方法与非虚方法
JVM的实现机制
- 虚方法调用
JVM使用虚方法表(vtable)实现动态分派。每个类维护一个虚方法表,记录方法的实际入口地址。调用时根据对象的实际类型查表,找到正确的方法实现。 - 非虚方法调用
直接通过符号引用在编译期确定调用目标,无需运行时查找。
虚方法(Virtual Method)
虚方法是支持动态绑定(运行时绑定)的方法,具体调用的方法实现由对象的实际类型(运行时类型)决定。Java中,默认情况下,未被final、private或static修饰的实例方法都是虚方法。
特点
-
动态绑定:方法调用在运行时根据对象的实际类型确定。
-
支持多态:允许子类重写(Override)父类方法,实现多态。
-
虚方法表(vtable):JVM通过虚方法表快速查找方法的实际实现。
class Animal {
public void speak() { // 虚方法(可被重写)
System.out.println("Animal speaks");
}
}
class Dog extends Animal {
@Override
public void speak() { // 重写父类方法
System.out.println("Dog barks");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.speak(); // 输出 "Dog barks"(动态绑定到Dog的speak方法)
}
}
常见虚方法
-
普通实例方法(未被final、private、static修饰)。
-
接口的默认方法(default方法)。
-
抽象方法(abstract方法)。
非虚方法(Non-Virtual Method)
非虚方法是静态绑定(编译时绑定)的方法,调用的具体方法在编译期就能确定,与对象的实际类型无关。这些方法无法被重写,或不需要动态分派。
特点
-
静态绑定:方法调用在编译时确定。
-
无法被重写:子类无法修改其行为。
-
性能更高:无需运行时查找方法表。
class Parent {
public static void staticMethod() { // 非虚方法(静态方法)
System.out.println("Parent's static method");
}
private void privateMethod() { // 非虚方法(private方法)
System.out.println("Parent's private method");
}
public final void finalMethod() { // 非虚方法(final方法)
System.out.println("Parent's final method");
}
}
class Child extends Parent {
// 尝试重写静态方法(实际是隐藏,而非重写)
public static void staticMethod() {
System.out.println("Child's static method");
}
// 无法重写private方法和final方法
}
public class Test {
public static void main(String[] args) {
Parent parent = new Child();
parent.staticMethod(); // 输出 "Parent's static method"(静态绑定)
}
}
常见的非虚方法
-
静态方法(static):属于类,调用时基于引用类型。
-
私有方法(private):仅在类内部可见,无法被重写。
-
final方法:禁止子类重写。
-
构造方法:隐式调用,无法被动态分派。
-
通过super调用的父类方法:直接指定父类实现。
方法返回地址
当一个方法开始执行之后,只有两种可能:
- 正常结束,当前方法栈帧出栈, 返回上一个方法的栈帧;
- 异常结束,如果本方法没有处理异常的方法,方法就会异常退出,不会给调用者提供任何返回值
无论是怎样退出,在方法退出之后,都需要恢复到被调用之前的那个方法的栈帧的当时的状态,程序才能正常往下执行。从栈的角度,方法退出,实际上是当前栈帧出栈,要恢复上层方法的局部变量表与操作数栈,如果有返回值,还需要把返回值压入操作数栈,然后将程序计数器指向上层方法的下一条指令的地址。