方法解析:
Class文件编译过程一切方法调用在Class文件里存储的只是符号引用,此特性给java带来强大的动态扩展能力,支持动态连接(在类运行期确定某些目标方法的直接引用),也有一部分方法的符号引用在类加载阶段或第一次使用时转为直接引用(静态解析)
静态解析:
成立的前提:方法在程序真正执行前便有一个确定的在运行期不变的调用版本(调用目标在编译器进行编译时就确定了下来),这类方法的调用称为解析
编译期可知,运行期不可变的方法:
静态方法:与类型直接关联;私有方法:外部不可访问
均不可通过继承或别的方式重写出其他版本,适合类加载阶段进行解析
Java虚拟机提供四条方法调用字节指令:
invokestatic:调用静态方法
invokespecial:调用实例构造器方法、私有方法和父类方法
invokevirtual:调用所有的虚方法
invokeinterface:调用接口方法,运行时确定实现此接口的对象
解析调用:静态过程,编译期确定,类加载的解析阶段将涉及的符号引用转为确定的直接引用
分派调用:可静可动、根据分派依据的宗量数(宗量:方法调用者和参数)分为单多分派
Human man = new Man();
Human称为变量的静态类型,Man变量的实际类型,这两种类型在程序中均可发送变化
静态类型的变仅仅在使用时发生,变量本身的静态类型不会被改变
静态类型编译期可知,实际类型的变化结果在运行期才确定
class Human{
}
class Man extends Human{
}
class Woman extends Human{
}
public class StaticPai{
public void say(Human hum){
System.out.println("I am human");
}
public void say(Man hum){
System.out.println("I am man");
}
public void say(Woman hum){
System.out.println("I am woman");
}
public static void main(String[] args){
Human man = new Man();
Human woman = new Woman();
StaticPai sp = new StaticPai();
sp.say(man);
sp.say(woman);
}
}
调say时,方法调用者都为sp的前提下,使用哪个重载版本,取决于传入参数的数量和数据类型,代码刻意定义了两个静态类型相同、实际类型不同的变量,编译器在重载时是通过参数的静态类型作为判定依据决定使用哪个重载版本:静态分派典型应用
动态分派:
方法重写关系紧密,向上转型后调用子类覆写的方法很好的例子,根据对父类实例化的子类不同、调用的是不同子类中覆写的方法:根据变量实际类型来分派方法的执行版本,实际类型在程序运行时才确定
class Eat{
}
class Drink{
}
class Father{
public void doSomething(Eat arg){
System.out.println("爸爸在吃饭");
}
public void doSomething(Drink arg){
System.out.println("爸爸在喝水");
}
}
class Child extends Father{
public void doSomething(Eat arg){
System.out.println("儿子在吃饭");
}
public void doSomething(Drink arg){
System.out.println("儿子在喝水");
}
}
public class SingleDoublePai{
public static void main(String[] args){
Father father = new Father();
Father child = new Child();
father.doSomething(new Eat());
child.doSomething(new Drink());
}
}
编译阶段编译器的选择:静态分派过程(很多情况下这个重载的版本并不唯一,更合适)
选择目标方法的依据:方法的调用者的静态类型是Father还是Child,二方法参数类型是Eat还是Drink:宗量
运行阶段虚拟机的选择:动态分派过程
编译期已确定了目标方法的参数类型(编译期据参数的静态类型进行静态分派),唯一可影响到虚拟机选择的因素只有调用者的实际类型是Father还是Child,一个宗量:单分派类型