方法调用过程(一)

      方法的调用不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。
      一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局的入口地址(入口地址相当于直接引用),需要在类加载期间甚至到运行期间才能确定目标方法的直接引用。
      通过javap -verbose+类名 可以看到,Java虚拟机中提供了四条方法调用字节码指令,
     1.invokestatic  调用静态方法
     2.invokespecial  调用实例构造器(init)方法、私有方法和父类方法
     3.invokevirtual  调用所有的虚方法
     4.invokeinterface 调用接口方法,会在运行时再确定一个实现此接口的对象
     先来看下面两种方法调用方式:
     解析调用:方法在程序真正运行前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。换句话说,调用目标在程序代码写好、编译器进行编译时就必须确定下来。(一定是静态的)
     分派调用:方法在程序运行期间才确定调用版本,分派调用分为静态分派和动态分派
     再来理解一个概念:
     非虚方法:在类加载的时候就会把符号引用解析为该方法的直接引用,就是说能在解析阶段确定唯一的方法调用版本的方法称为非虚方法。只要能被invokestatic和invokespecial指令调用的方法,都符合非虚条件,包括了静态方法、私有方法、实例构造器和父类方法四类,还有一个特殊的final方法,它虽然通过invokevirtual调用,但Java语言规范中明确说明了final方法是一种非虚方法。
     虚方法:与之相反的,在运行期才能确定方法调用版本。
     所以非虚方法都是通过解析调用(静态),而虚方法都是通过分派调用(静态或者动态)。
     那么在分派和解析调用中的静态和动态的概念又怎么理解呢,请看下面这个例子:
           Human person = new Man();
     Man是Human的一个子类,那么Human就是实例person的静态类型,Man是person的实际类型。
  
    1.下面这段代码演示了方法重载(overload)时是通过静态分派的:
    

public class StaticDispatch {
	
	static abstract class Human{
		
	}
	
	static class Man extends Human{
		
	}
	
	static class Woman extends Human{
		
	}
	
	public void sayHello(Human m)
	{
		System.out.println("hello, guy!");
	}
	
	public void sayHello(Man m)
	{
		System.out.println("hello, man!");
	}
	
	public void sayHello(Woman m)
	{
		System.out.println("hello, woman!");
	}
	
	public static void main(String[] args) throws Exception {
		Human man = new Man();
		Human woman = new Woman();
		StaticDispatch t = new StaticDispatch();
		t.sayHello(man);
		t.sayHello(woman);
		
	}

}

 
    运行结果是:
    hello, guy!
    hello, guy!
  
    2.下面这段代码演示了方法重写(overwrite)时是动态分派的:
    

public class DynamicDispatch {

	static abstract class Human{
		protected abstract void sayHello();
	}
	
	static class Man extends Human{
		@Override
		protected void sayHello() {
			System.out.println("hello, man!");
		}
	}
	
	static class Woman extends Human{

		@Override
		protected void sayHello() {
			System.out.println("hello, woman!");
		}
	}

	public static void main(String[] args) {
		Human man = new Man();
		Human woman = new Woman();
		man.sayHello();
		woman.sayHello();
	}

}

 
    运行结果是:
    hello, man!
    hello, woman!
  
  
    接下来看以下invokevirtual指令调用方法的过程就可以理解为什么如此了:
  (1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C
  (2)如果在类型C中找到与常量中描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找结束;不通过,返回IllegalAccessError
  (3)否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程
  (4)始终没有找到合适的方法,则抛出AbstractMethodError
  
    其实方法的重载和重写区别就是重载依赖传入方法的形参来区别,而重写依赖方法调用者的类型来区别。
    在第一步中C就是方法的调用者,这里查找的是实际类型,这就是overwrite的原理,
    在第二步中查找方法是通过描述符和简单名称来进行的,而传入参数的描述符是通过静态类型定义的,所以代码一会出现这样的结果
    总结一句话,重载看静态类型,重写看实际类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值