解析

在 Java 虚拟机里面提供了 5 条方法调用字节码指令,分别如下。

  • invokestatic:调用静态方法。
  • invokespecial:调用实例构造器 <init> 方法、私有方法和父类方法。
  • invokevirtual::调用所有的虚方法。
  • invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
  • invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,再次之前的 4 条调用指令,分派逻辑是固化在 Java 虚拟机内部的,而 invokedynamic 指令的分配逻辑是由用户所设定的引导方法决定的。

只要能被 invokestatic 和 invokespecial 指令调用的方法,都可以在解析阶段确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法 4 类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法,与之相反,其他方法称为虚方法(除去 final 方法)。

分派

        众所周知,Java 是一门面向对象的程序语言,因为 Java 具备面向对象的 3 个基本特征:继承、封装和多态。本节讲解的分派调用过程将会揭示多态性特征的一些最基本的体现,如 “重载” 和 “重写” 在 Java 虚拟机之中是如何实现的,这里的实现当然不是语法上该如何写,我们关心的依然是虚拟机如何确定正确的目标方法。

1.静态分派(重载)  

package org.fenixsoft.polymorphic;
 
/**
 * 方法静态分派演示
 *
 */
public class StaticDispatch {
 
	static abstract class Human {
		
	}
	
	static class Man extends Human {
		
	}
	
	static class Woman extends Human {
		
	}
	
	public void sayHello(Human guy) {
		 System.out.println("hello, guy!");
	}
	
	public void sayHello(Man guy) {
		System.out.println("hello,gentleman");
	}
	
	public void sayHello(Woman guy) {
		System.out.println("hello,lady!");
	}
	
	public static void main(String[] args) {
		Human man = new Man();
		Human woman = new Woman();
		
		StaticDispatch sr = new StaticDispatch();
		sr.sayHello(man);//hello, guy!
		sr.sayHello(woman);//hello, guy!
	}
}

    我们把上面代码中的 “Human” 称为变量的静态类型(Static Type),或者叫做外观类型(Apparent Type),后面的 “Man” 则称为变量的实际类型(Actual Type),静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定。

重载时是通过参数的静态类型而不是实际类型作为判定依据的。所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载。

2.动态分派(重写)

package org.fenixsoft.polymorphic;
 
/**
 * 方法动态分派演示
 *
 */
public class DynamicDispatch {
 
	static abstract class Human {
		protected abstract void sayHello();
	}
	
	static class Man extends Human {
		@Override
		protected void sayHello() {
			System.out.println("man say hello");
		}
	}
	
	static class Woman extends Human {
		@Override
		protected void sayHello() {
			System.out.println("woman say hello");
		}
	}
	
	public static void main(String[] args) {
		Human man = new Man();
		Human woman = new Woman();
		man.sayHello();     //man say hello
		woman.sayHello();   //woman say hello
		man = new Woman();  
		man.sayHello();     //woman say hello
	}
}

在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。动态分派的典型应用是重写。