在Java虚拟机(JVM)中,方法可以分为虚方法和非虚方法。虚方法是指在运行时由实例的实际类型决定的方法,
而非虚方法则是在编译时就可以确定调用哪个方法。这两种方法在JVM中的实现方式有所不同,下面将详细介绍它们的区别。
在Java虚拟机中,调用方法的指令有以下几种:
- invokestatic:调用静态方法
- invokespecial:调用实例构造器方法、私有方法和父类方法
- invokevirtual:调用所有虚方法,包括接口方法和非私有实例方法
- invokeinterface:调用接口方法
- invokedynamic:动态调用方法
1. 虚方法
虚方法是指在运行时由实例的实际类型决定的方法。在Java中,所有的非私有、非静态、非final方法都是虚方法。当调用一个虚方法时,JVM会根据实例的实际类型来确定要调用哪个方法。
例如,假设我们有一个父类Animal和一个子类Dog:
public class Animal {
public void makeSound() {
System.out.println("Animal makes sound");
}
}
public class Dog extends Animal {
public void makeSound() {
System.out.println("Dog barks");
}
}
如果我们创建一个Animal类型的实例,并调用它的makeSound()方法,输出将是“Animal makes sound”。但是,如果我们创建一个Dog类型的实例,并调用它的makeSound()方法,输出将是“Dog barks”。这是因为在运行时,JVM会根据实例的实际类型来确定要调用哪个makeSound()方法。
2. 非虚方法
非虚方法是指在编译时就可以确定调用哪个方法的方法。在Java中,私有方法、静态方法和final方法都是非虚方法。这些方法在编译时就可以确定调用哪个方法,因此不需要在运行时进行动态绑定。
例如,假设我们有一个父类Animal和一个子类Dog:
public class Animal {
private void makeSound() {
System.out.println("Animal makes sound");
}
public static void printName() {
System.out.println("Animal");
}
public final void eat() {
System.out.println("Animal eats");
}
}
public class Dog extends Animal {
private void makeSound() {
System.out.println("Dog barks");
}
public static void printName() {
System.out.println("Dog");
}
// 编译错误:无法覆盖final方法
// public void eat() {
// System.out.println("Dog eats");
// }
}
无论我们创建Animal类型的实例还是Dog类型的实例,并调用它们的私有方法makeSound()、静态方法printName()或final方法eat(),
输出都将是“Animal makes sound”、“Animal”和“Animal eats”。这是因为这些方法在编译时就可以确定调用哪个方法,而不需要在运行时进行动态绑定。
3. 虚方法表
为了支持动态绑定,JVM使用了虚方法表来存储每个类的虚方法信息。虚方法表是一个类的数据结构,它包含了该类所有虚方法的地址。
每个实例都包含一个指向其类的虚方法表的指针。当调用一个虚方法时,JVM会根据实例的实际类型来查找该类型的虚方法表,并从中获取该方法的地址。
4. 总结
在Java中,虚方法和非虚方法是两种不同的方法类型。虚方法是在运行时由实例的实际类型决定的方法,而非虚方法则是在编译时就可以确定调用哪个方法。
为了支持动态绑定,JVM使用了虚方法表来存储每个类的虚方法信息。了解这些概念对于理解Java中的多态性和继承机制非常重要。