方法调用指令
- invokestatic: 调用静态方法
- invokespecial: 调用实例构造器<init>方法、私有方法和父类方法
- invokevirtual: 调用所有的虚方法
- invokeinterface: 调用接口方法,会在运行时在确定一个实现此接口的对象
- invokedynamic: 先在运行时动态解析出调用点限定符所引用的方法,再执行
非虚方法:静态方法、私有方法、实例构造器、父类方法
虚方法:除了非虚方法和final方法,都称为虚方法(但是final是使用invokevirtual指令来调用的)
什么是方法调用?
方法的调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。
为什么要了解方法调用?
加深对Java多态的理解
Java的方法调用为什么这么复杂?
Class文件的编译(Javac)过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(即直接引用)。这个特性给Java带来了更强大的动态扩展能力,但也使得Java方法调用过程变得相对复杂起来,需要在类加载期间,甚至到运行期间才能确定目标方法的直接引用。
解析
解析相对于方法调用而言:在类加载的解析阶段,一部分目标方法的符号引用转化为直接引用。
这种解析成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的,即调用目标在程序代码写好、编译器进行编译时就必须确定下来。
“编译期可知,运行期不可变”,主要包括静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这类方法不会被重写,所以适合在类加载阶段进行解析。
解析调用一定是个静态的过程,在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期再去完成。
分派
分派调用可能是静态的也可能是动态的,根据分派的宗量数可分为单分派和多分派。这两类分派方式的两两组合就构成了静态单分派、静态多分派、动态单分派、动态多分派。
宗量:方法的接收者与方法的参数统称为方法的宗量。单分派是根据一个宗量对目标方法进行选择,多分派则是根据多于一个宗量对目标方法进行选择。
静态类型与实际类型
/**
* @author zhuzhu
* @date 2021/9/4 - 14:00
* 静态类型和实际类型
*/
public class Dispatch {
/**
* List list1 = new ArrayList();
* 对于这行代码来说,前面的List成为变量的静态类型(Static Type),或者叫做外观类型(Apparent Type)
* 后面的ArrayList成为实际类型(Actual Type)
*/
List list1 = new ArrayList();
List list2 = new LinkedList();
// 改变静态类型
private void changeStaticType() {
list1 = getArrarList(list1);
}
// 改变实际类型
private void changeActualType() {
list1 = new LinkedList();
}
private ArrayList getArrarList(List list) {
return (ArrayList) list;
}
}
静态分派
所有依赖静态类型来定位方法执行版本的分派动作成为静态分派。静态分派的典型应用是方法重载。
/**
* @author zhuzhu
* @date 2021/9/4 - 14:47
* 方法静态分派演示
*/
public class StaticDispatch {
static class Animal {}
static class Dog extends Animal {}
static class Cat extends Animal {}
public static void sayHello(Animal animal) {
System.out.println("hello animal");
}
public static void sayHello(Dog dog) {
System.out.println("hello dog");
}
public static void sayHello(Cat cat) {
System.out.println("hello cat");
}
public static void main(String[] args) {
Animal animal = new Animal();
Dog dog = new Dog();
Cat cat = new Cat();
sayHello(animal);
sayHello(dog);
sayHello(cat);
System.out.println("**********************************");
Animal dog1 = new Dog();
Animal cat1 = new Cat();
sayHello(dog1);
sayHello(cat1);
}
}
动态分派
/**
* @author zhuzhu
* @date 2021/9/4 - 17:14
* 方法动态分派
*/
public class DynamicDispatch {
static class Animal {
public void sayHello() {
System.out.println("hello animal...");
}
public void sayHello(Object o) {
System.out.println("hello animal:" + o);
}
}
static class Dog extends Animal {
public void sayHello() {
System.out.println("hello dog...");
}
public void sayHello(Object o) {
System.out.println("hello dog:" + o);
}
}
static class Cat extends Animal {
public void sayHello() {
System.out.println("hello cat...");
}
public void sayHello(Object o) {
System.out.println("hello cat:" + o);
}
}
public static void main(String[] args) {
Animal animal = new Animal();
animal.sayHello();
animal.sayHello(animal);
Animal animal1 = new Dog();
animal1.sayHello();
animal1.sayHello(animal1);
Animal animal2 = new Cat();
animal2.sayHello();
animal2.sayHello(animal2);
}
}
分派小结:今天的Java语言是一门静态多分派、动态单分派的语言。